Components

ArmenusModel renders the dish, works out what this device can show, offers AR when it is genuinely available, and falls back to the poster image when it is not.

Dish.tsx
<ArmenusModel
  item={item}
  arLabel="See it on your table"
  onEnterAr={(item) => analytics.track("ar_opened", item.id)}
  footer={(p) =>
    p.ar.blockedOnConversion
      ? <p>AR is nearly ready — check back in a minute.</p>
      : null
  }
/>

Live

The web component, running. The sliders write into viewSettings— the same field a restaurant edits in the dashboard — so this is the operator’s control surface, not a docs-only affordance.

Live demo not configured

Set NEXT_PUBLIC_ARMENUS_DEMO_KEY to a publishable key for your demo restaurant and this becomes an interactive viewer. Everything else on this page works without it.

Props

itemEmbedItem | nullrequired

The dish. null renders the loading state, so a hook’s data can be passed straight through.

arLabelstringoptional

Label on the AR button. Defaults to "View on your table".

interactionEnabledbooleannative only

Whether a drag rotates the model. Pass false inside a scrolling list — on a phone the two gesture systems fight, and a card that swallows vertical drags makes the whole feed feel stuck.

footer(p: Presentation) => Nodeoptional

Rendered beneath the canvas with the resolved capability state, so your copy can adapt to what this particular device can do.

onEnterAr(item: EmbedItem) => voidoptional

Fires when the user activates AR. For your own analytics.

className / styleoptional

On web, styles.css is optional and everything is overridable through --armenus-* custom properties. The package ships no utility-class dependency, because it has to work in Tailwind, CSS modules, styled-components and nothing at all.

resolvePresentation()

The capability brain. Given a platform and a model, it returns what to render and whether AR can be offered. Every SDK funnels through it, so the four cannot drift apart. See Formats & platforms for why the rules are not symmetric.

@armenus/sdk-core
const { inline, ar } = resolvePresentation({
  platform: "ios",            // "ios" | "android" | "web-other"
  model: item.model,
  arCoreAvailable: true,      // Android — must be probed natively
});

inline.kind  // "glb" | "usdz" | "poster" | "none"
inline.url   // what to load, when there is something to load

ar.mode      // "webxr" | "scene-viewer" | "quick-look" | "none"
ar.supported // can this user place the dish in their room?
ar.reason    // why not — written to be shown to a person
ar.blockedOnConversion  // only the USDZ is missing; it is coming

Probing the device

arCoreAvailable must come from the platform. Defaulting it to true is a real bug: an AR button on a handset without ARCore opens the Play Store instead of the camera, which reads to the user as a broken app.

PlatformProbeWhy not a version check
iOSARWorldTrackingConfiguration.isSupportedAn iPhone 6 on iOS 15 passes a version check and has no world tracking.
AndroidArCoreApk.checkAvailabilityAnswers both “supported device?” and “ARCore installed?” — a version check answers neither.
Webnavigator.xr.isSessionSupportedA Permissions-Policyheader can reject the probe outright; treat that as “no”.

ArmenusModel does all of this on mount. It matters only if you are building on sdk-core directly.

Permissions

The SDKs add no permissions to your app. AR placement runs in the system AR viewer — Quick Look on iOS, Scene Viewer on Android — which is a separate app holding the camera permission in its own process. Your Play Store data-safety declaration and your iOS privacy manifest are unchanged by installing us.

ARKit is linked on iOS only for ARWorldTrackingConfiguration.isSupported and ARQuickLookPreviewItem; no ARSession is ever created. Android never links the ARCore SDK for rendering at all.