run() answers needs_connection, holds the question, and includes the URL to offer:
The click, start to finish
<ConnectButton>opens Payman’s consent page in a popup. It renders a real link as the blocked-popup fallback and retires itself once connected.- The customer signs in, picks a provider, and approves access to their Payman account.
- Payman redirects to the mounted callback, which verifies
state, exchanges the code, and stores the connection. - Before redirecting back, the callback starts the held question server-side. The reply is already being produced while the browser navigates.
- The page lands with
?payman=connected(plusresume=1when a replay started).<PaymanProvider autoResume>opens one stream on{basePath}/resume/streamand the answer arrives with nothing retyped.
?payman=denied. Treat it as an answer, not an error, and offer again when a later turn needs it.
Watching the replay
Progress frames carry a phase and the platform’s own label, nothing else. Show
progress.label while the turn runs rather than the raw liveText; <ResumeNotice> does exactly that and hides once output lands.
Refresh and multiple instances
The resume stream attaches instead of starting. A refresh mid-replay catches up on buffered frames and follows live, and the held question can never run twice. When the callback landed on another instance, the stream falls back to the shared store, so multi-instance deployments need the shared store they already chose (see any framework). Off the browser there is no page to attach. After handling your callback, callresumePending() to run the held question yourself.

