| Empty band at the top on iPhone | Missing viewport-fit=cover in the viewport meta tag. |
| Content hidden under the notch or home indicator | Pad with env(safe-area-inset-*), var(--sat) etc., or useSafeArea(). |
| Top-right content covered | The host's ⋮ / ✕ buttons sit there. Leave about 90 × 40 px free. |
| "Took too long to load" warning | Your page took over 15 s to load and never called sdk.ready(). Call it after the first paint. |
getSDK() is null | You are in a normal browser. Use a mock bridge. |
showToast / navigate does nothing | The permission was refused or not granted, or the route is not in ALLOWED_ROUTES. Fire-and-forget calls fail silently. |
PERMISSION_DENIED | The user said no. Declare the permission at registration and handle the refusal. |
earnPoints rejects INVALID_PARAMS | action is not one of EARN_POINTS_ACTIONS. |
| Shared link missing on Android | Android ignores the share url. Put the link in message / text. |
whenNativeReady() never resolves | You subscribed after the event. Call it at module top level. |
scanQr rejects UNSUPPORTED in the app | The user's app version predates scanning. Hide the button with hasBridgeMethod('scanQr'). |
| Text cannot be selected | Selection is off by default. Call sdk.setTextSelectable(true). |