Open a page outside this preview.
Visit example.com{state.status}
> } ``` Handle rejected preparation in your app. A real app can prepare when its sign-in view appears and enable Sign in when ready. `start()` defaults to popup. If it reports `popup-blocked`, offer a new user action calling `request.start({ transport: 'redirect' })`. Redirect replaces the app document. The old JavaScript promise cannot receive its completion; `useWebAuthentication` reconciles durable state when the app launches again. A popup closed by the user and an opener severed by browser isolation can look identical. The state remains pending with `needsReturn: true`; check completion, offer Return and Cancel, and let the transaction expire. To choose redirect after an already-started popup, cancel it, prepare a fresh request and start that request as redirect. A fresh request prevents an old callback from consuming the new sign-in attempt. The callback is a dedicated same-origin document outside the app scope. It imports the same declaration, calls `await authentication.complete()`, and shows a Return to app link using the returned `returnURL`. A popup may offer `window.close()` on that callback document. Returning through an ordinary link can open a new app document or Safari tab; the platform does not guarantee automatic reopening of the installed app. The user can return through Home Screen and the result will reconcile there. The callback must have `Cache-Control: no-store`, `Referrer-Policy: no-referrer`, no analytics, and no third-party subresources. Serve it independently of the app platform/install gate. Keep bridge, exchange and IdP routes out of the app-shell cache. The framework worker passes authorization responses containing state plus code/error through to the network. The helper checks exact callback origin/path, state, response shape, expiry and replay. It stores a callback before removing response parameters with the framework URL projection. It sends only a wake-up notice through `postMessage`, with an exact origin; the receiver also checks the popup source. Codes, verifiers and tokens are never messaged. The pending verifier lives in origin-local storage until completion, cancellation or expiration. A terminal result contains no code, verifier, nonce or bridge proof. `exchange` establishes the server session and resolves only after success. Use `transactionID` as an idempotency key: process eviction after exchange but before the local success write can cause a retry. Verify the code, PKCE verifier, callback and configured provider on the server. For OpenID Connect also validate the token signature, issuer, audience, time claims and supplied nonce. The helper supplies `nonce` and `expectedIssuer`; it does not validate identity tokens. An `iss` response parameter is checked when present; server token issuer validation is required even when it is absent. Use distinct callback URLs for providers that do not identify their issuer in the authorization response. Never put a client secret in this bundle. The helper requires secure context, local storage and Web Locks. HTTP loopback is accepted for local development. It cannot complete while the app is suspended; it resumes on launch, pageshow, visibility, focus or a callback wake-up. Storage and network errors are explicit. Cancellation aborts a cooperating exchange and preserves the cancelled local result; it cannot revoke a session already created by your server. ## Optional server bridge Use a bridge when the callback cannot access the initiating app's storage or opener. It is a same-origin HTTPS POST endpoint outside the app scope. All responses are no-store JSON. The request includes `operation`, `id`, `state` and the fields below. | Operation | Request fields | Response | | --- | --- | --- | | register | proofHash, timeoutMs | { accepted: true } | | complete | response: { state, code } or { state, error } | { accepted: true } | | read | proof | null or the stored response | | clear | proof | { accepted: true } | Register stores the SHA-256 base64url hash of a fresh 256-bit private proof, a bounded expiry and app/provider binding. Read and clear must hash and compare that proof. The proof never appears in the provider URL or callback. Knowledge of state alone must not permit reading or claiming a login. Complete must atomically accept only the first response for an unexpired registered state, reject malformed inputs, and never mint a session. The final server exchange still verifies PKCE. Reject cross-origin requests, bound sizes and rates, and expire abandoned transactions. A non-read success must explicitly return `accepted: true`; HTTP 200 alone is insufficient. The fixture in `fixtures/web-handoff` implements this protocol and verifies one-use PKCE codes. It demonstrates OAuth code exchange with PKCE, not OpenID Connect identity-token validation. Its in-memory IdP and bridge are for local testing only; use durable transactional server storage in production. --- Canonical page: https://satin.build/docs/web/use-open-url.html Source: docs/web/use-open-url.md # useOpenURL Use `NavigationLink` for typed screen destinations. Use `Link` or `useOpenURL` for a URL. Registered URLs in your app scope use the existing navigation stack and retain the document. An unknown app URL returns `unavailable`; it never turns a route typo into a page reload. ```tsx import { useState } from 'react' import { Button, Screen, useOpenURL } from '@satinhq/kit' function Example() { const openURL = useOpenURL() const [result, setResult] = useState('No request yet') return ({result}
{state.status}
> } ``` Handle rejected preparation in your app. A real app can prepare when its sign-in view appears and enable Sign in when ready. `start()` defaults to popup. If it reports `popup-blocked`, offer a new user action calling `request.start({ transport: 'redirect' })`. Redirect replaces the app document. The old JavaScript promise cannot receive its completion; `useWebAuthentication` reconciles durable state when the app launches again. A popup closed by the user and an opener severed by browser isolation can look identical. The state remains pending with `needsReturn: true`; check completion, offer Return and Cancel, and let the transaction expire. To choose redirect after an already-started popup, cancel it, prepare a fresh request and start that request as redirect. A fresh request prevents an old callback from consuming the new sign-in attempt. The callback is a dedicated same-origin document outside the app scope. It imports the same declaration, calls `await authentication.complete()`, and shows a Return to app link using the returned `returnURL`. A popup may offer `window.close()` on that callback document. Returning through an ordinary link can open a new app document or Safari tab; the platform does not guarantee automatic reopening of the installed app. The user can return through Home Screen and the result will reconcile there. The callback must have `Cache-Control: no-store`, `Referrer-Policy: no-referrer`, no analytics, and no third-party subresources. Serve it independently of the app platform/install gate. Keep bridge, exchange and IdP routes out of the app-shell cache. The framework worker passes authorization responses containing state plus code/error through to the network. The helper checks exact callback origin/path, state, response shape, expiry and replay. It stores a callback before removing response parameters with the framework URL projection. It sends only a wake-up notice through `postMessage`, with an exact origin; the receiver also checks the popup source. Codes, verifiers and tokens are never messaged. The pending verifier lives in origin-local storage until completion, cancellation or expiration. A terminal result contains no code, verifier, nonce or bridge proof. `exchange` establishes the server session and resolves only after success. Use `transactionID` as an idempotency key: process eviction after exchange but before the local success write can cause a retry. Verify the code, PKCE verifier, callback and configured provider on the server. For OpenID Connect also validate the token signature, issuer, audience, time claims and supplied nonce. The helper supplies `nonce` and `expectedIssuer`; it does not validate identity tokens. An `iss` response parameter is checked when present; server token issuer validation is required even when it is absent. Use distinct callback URLs for providers that do not identify their issuer in the authorization response. Never put a client secret in this bundle. The helper requires secure context, local storage and Web Locks. HTTP loopback is accepted for local development. It cannot complete while the app is suspended; it resumes on launch, pageshow, visibility, focus or a callback wake-up. Storage and network errors are explicit. Cancellation aborts a cooperating exchange and preserves the cancelled local result; it cannot revoke a session already created by your server. ## Optional server bridge Use a bridge when the callback cannot access the initiating app's storage or opener. It is a same-origin HTTPS POST endpoint outside the app scope. All responses are no-store JSON. The request includes `operation`, `id`, `state` and the fields below. | Operation | Request fields | Response | | --- | --- | --- | | register | proofHash, timeoutMs | { accepted: true } | | complete | response: { state, code } or { state, error } | { accepted: true } | | read | proof | null or the stored response | | clear | proof | { accepted: true } | Register stores the SHA-256 base64url hash of a fresh 256-bit private proof, a bounded expiry and app/provider binding. Read and clear must hash and compare that proof. The proof never appears in the provider URL or callback. Knowledge of state alone must not permit reading or claiming a login. Complete must atomically accept only the first response for an unexpired registered state, reject malformed inputs, and never mint a session. The final server exchange still verifies PKCE. Reject cross-origin requests, bound sizes and rates, and expire abandoned transactions. A non-read success must explicitly return `accepted: true`; HTTP 200 alone is insufficient. The fixture in `fixtures/web-handoff` implements this protocol and verifies one-use PKCE codes. It demonstrates OAuth code exchange with PKCE, not OpenID Connect identity-token validation. Its in-memory IdP and bridge are for local testing only; use durable transactional server storage in production. --- Canonical page: https://satin.build/docs/web/use-web-frame.html Source: docs/web/use-web-frame.md # useWebFrame ```tsx import { Button, Screen, WebView, useWebFrame } from '@satinhq/kit' function Example() { const frame = useWebFrame() return (Frame: {frame.state.status}
{frame.state.status}
> } ``` `WebView` embeds an iframe. Its parent supplies a height. Scripts and form submission are disabled unless `allowsJavaScript` or `allowsForms` is true. Popups, downloads and top navigation remain sandboxed. Same-origin scripted content must be trusted: `allow-scripts` with `allow-same-origin` is not a security boundary against your own origin. `loaded` means a same-origin document is inspectable after its load event. It does not assert an HTTP success status or application readiness. Foreign or denied frames report `unverified` with `cross-origin-or-denied`. A browser does not reliably reveal which happened. Offer an ordinary Link when embedding is unavailable; never proxy around a page's framing restrictions. `useWebFrame` returns a controller with `state`, `reload()` and `stop()`. Both commands return `unavailable` for foreign, denied or unmounted frames. There is no invented cross-origin title, progress, script evaluation or browser-history API. Embedded navigation can participate in browser session history; qualify your content's edge-back, keyboard and scrolling behavior. Embedded storage can be partitioned. Use the authentication helper for sign in. ## Authorization-code sign in Declare one authentication object outside rendering. Generate PKCE before the tap, then dispatch the prepared request synchronously. ```tsx import { defineWebAuthentication, useWebAuthentication, Button } from '@satinhq/kit' import { useState } from 'react' import type { WebAuthenticationRequest } from '@satinhq/kit' const authentication = defineWebAuthentication({ id: 'account', appURL: 'https://app.example/app/', callbackURL: 'https://app.example/auth/callback', authorizationURL: 'https://accounts.example/authorize', clientID: 'public-client-id', scopes: ['openid', 'profile'], issuer: 'https://accounts.example', bridgeURL: 'https://app.example/auth/bridge', exchange: async ({ signal, ...request }) => { const response = await fetch('/auth/exchange', { method: 'POST', credentials: 'same-origin', cache: 'no-store', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), signal, }) if (!response.ok) throw new Error('Sign in failed') }, }) function SignIn() { const state = useWebAuthentication(authentication) const [request, setRequest] = useState{state.status}
> } ``` Handle rejected preparation in your app. A real app can prepare when its sign-in view appears and enable Sign in when ready. `start()` defaults to popup. If it reports `popup-blocked`, offer a new user action calling `request.start({ transport: 'redirect' })`. Redirect replaces the app document. The old JavaScript promise cannot receive its completion; `useWebAuthentication` reconciles durable state when the app launches again. A popup closed by the user and an opener severed by browser isolation can look identical. The state remains pending with `needsReturn: true`; check completion, offer Return and Cancel, and let the transaction expire. To choose redirect after an already-started popup, cancel it, prepare a fresh request and start that request as redirect. A fresh request prevents an old callback from consuming the new sign-in attempt. The callback is a dedicated same-origin document outside the app scope. It imports the same declaration, calls `await authentication.complete()`, and shows a Return to app link using the returned `returnURL`. A popup may offer `window.close()` on that callback document. Returning through an ordinary link can open a new app document or Safari tab; the platform does not guarantee automatic reopening of the installed app. The user can return through Home Screen and the result will reconcile there. The callback must have `Cache-Control: no-store`, `Referrer-Policy: no-referrer`, no analytics, and no third-party subresources. Serve it independently of the app platform/install gate. Keep bridge, exchange and IdP routes out of the app-shell cache. The framework worker passes authorization responses containing state plus code/error through to the network. The helper checks exact callback origin/path, state, response shape, expiry and replay. It stores a callback before removing response parameters with the framework URL projection. It sends only a wake-up notice through `postMessage`, with an exact origin; the receiver also checks the popup source. Codes, verifiers and tokens are never messaged. The pending verifier lives in origin-local storage until completion, cancellation or expiration. A terminal result contains no code, verifier, nonce or bridge proof. `exchange` establishes the server session and resolves only after success. Use `transactionID` as an idempotency key: process eviction after exchange but before the local success write can cause a retry. Verify the code, PKCE verifier, callback and configured provider on the server. For OpenID Connect also validate the token signature, issuer, audience, time claims and supplied nonce. The helper supplies `nonce` and `expectedIssuer`; it does not validate identity tokens. An `iss` response parameter is checked when present; server token issuer validation is required even when it is absent. Use distinct callback URLs for providers that do not identify their issuer in the authorization response. Never put a client secret in this bundle. The helper requires secure context, local storage and Web Locks. HTTP loopback is accepted for local development. It cannot complete while the app is suspended; it resumes on launch, pageshow, visibility, focus or a callback wake-up. Storage and network errors are explicit. Cancellation aborts a cooperating exchange and preserves the cancelled local result; it cannot revoke a session already created by your server. ## Optional server bridge Use a bridge when the callback cannot access the initiating app's storage or opener. It is a same-origin HTTPS POST endpoint outside the app scope. All responses are no-store JSON. The request includes `operation`, `id`, `state` and the fields below. | Operation | Request fields | Response | | --- | --- | --- | | register | proofHash, timeoutMs | { accepted: true } | | complete | response: { state, code } or { state, error } | { accepted: true } | | read | proof | null or the stored response | | clear | proof | { accepted: true } | Register stores the SHA-256 base64url hash of a fresh 256-bit private proof, a bounded expiry and app/provider binding. Read and clear must hash and compare that proof. The proof never appears in the provider URL or callback. Knowledge of state alone must not permit reading or claiming a login. Complete must atomically accept only the first response for an unexpired registered state, reject malformed inputs, and never mint a session. The final server exchange still verifies PKCE. Reject cross-origin requests, bound sizes and rates, and expire abandoned transactions. A non-read success must explicitly return `accepted: true`; HTTP 200 alone is insufficient. The fixture in `fixtures/web-handoff` implements this protocol and verifies one-use PKCE codes. It demonstrates OAuth code exchange with PKCE, not OpenID Connect identity-token validation. Its in-memory IdP and bridge are for local testing only; use durable transactional server storage in production. --- Canonical page: https://satin.build/docs/index.html Source: apps/docs/pages/home.md # Satin documentation Satin is a React framework for iPhone and iPad apps that people add to the Home Screen from Safari. It gives you screens and navigation, lists and forms, sheets and menus, local data, and glass rendered by the framework. This is a public beta for iOS 27. [Known issues](https://satin.build/docs/known-issues.html) lists what iOS limits, what we're fixing and what we won't do. ## Your first hour 1. [Quick start](https://satin.build/docs/learn/quick-start.html): create an app and open it on your iPhone. About 10 minutes. 2. [Build a list app](https://satin.build/docs/learn/lists-and-forms.html): a reading list with a field for adding books. About 15 minutes. 3. [Add it to the Home Screen](https://satin.build/docs/learn/installing-and-updates.html): install your app from Safari and open it like any other. About 5 minutes. 4. [Deploy and update](https://satin.build/docs/learn/deploy.html): put it on a static host, check it, and ship a new version. About 15 minutes. ## Find your way - [Components](https://satin.build/docs/components/index.html): one page per component, with a live preview and the API. - [Recipes](https://satin.build/docs/recipes/index.html): longer examples, such as a messaging thread or local data with backups. - [API reference](https://satin.build/docs/api/index.html): every export, generated from the package itself. ## For agents Every page is also plain Markdown at the same path. The index is `/llms.txt`; `/llms-full.txt` has every page in one file. --- Canonical page: https://satin.build/docs/components/list.html Source: docs/components/list.md # List A scrollable collection of rows. ```tsx import { LabeledContent } from '@satinhq/kit' ``` ## Example Read the three books. Each title and author occupy one row. ```tsx import { LabeledContent, List, Screen, Section } from '@satinhq/kit' function Example() { return (Your editor content goes here.
{stop}
))}The train leaves Kyoto at nine. Keep this note with your tickets.
The return journey leaves Osaka after lunch.
Meeting point: Kyoto Station, north entrance.