This document describes the client-visible state machine for the C1 channel.
- MVP-faithful: every state and transition exists in a running system.
- Informative, not normative: it explains one concrete UI realization built on top of the
normative contracts:
- REST control plane: rest_api_contract.md
- Stream delivery plane: stream_protocol.md
The authoritative protocol and API guarantees live in those two contracts. This document focuses on how a client UI stitches them together into a deterministic user flow.
- The client UX is modeled as a single-session funnel:
- Input → Topic selection → Topic result
- All analysis and entitlement are scoped to (sessionId, topic):
sessionIdcomes fromPOST /api/sessiontopicis chosen by the user on the topic selection step
- Visibility of results is controlled by stream events:
analysisChunk/responsedeliver contentpaywallinterrupts visibilityunlockdelivers post-entitlement access to the full result (stream does not resume)
- Session lifetime:
- Backend sessions have a finite TTL (implementation-defined)
- After TTL, any navigation using that
sessionIdis treated as SessionExpired
The Mermaid diagram in ../diagrams/client_state_machine.mmd is the canonical visual reference; this document binds each node to actual UI behavior and stream semantics.
The diagram defines the following conceptual UI states:
IdleEditingInputSessionCreatedTopicSelectionRequestingLoadingPreviewFullStreamingPaymentPendingPaidSessionExpired
In the MVP, these concepts are realized across three UI surfaces:
- Input surface (collects user input and creates a session)
- Topic selection surface (lets user pick
topicunder a givensessionId) - Result surface (connects to stream and renders delivery / paywall / unlock)
On the result surface, an internal UI flag may track finer-grained view states such as:
connecting, streaming, paywalled, waiting, completed, error. This is implementation detail; the diagram
states remain the conceptual source of truth.
Meaning
- No session exists yet.
- User has not started editing input.
Typical UI representation
- User is on the input surface; fields are empty or untouched.
Entry
- User opens the product URL.
Exit
Idle → EditingInputwhen the user begins interacting with the input surface.
Meaning
- User is actively editing the analysis input for a prospective session.
Typical UI representation
- User is still on the input surface with local form state being edited.
Exit
EditingInput → SessionCreatedon “submit input”:- Client performs client-side validation (implementation-defined).
- Client calls
POST /api/session(see rest_api_contract.md). - On success, backend returns
{ sessionId }. - Client navigates to the topic selection surface.
Errors during validation or session creation keep the client in EditingInput with inline error UX
(these generic error paths are intentionally not modeled in the primary diagram).
Meaning
- A session exists server-side; the client holds a
sessionId, but the user has not chosen atopicyet.
Typical UI representation
- A navigation transition occurs to the topic selection surface.
Exit
SessionCreated → TopicSelectionwhen the topic selection surface is rendered.
Meaning
- User chooses which logical topic to analyze under the current session.
Typical UI representation
- The topic selection surface shows a fixed list of topics.
Entry
- From
SessionCreated, or returning from a result state (Full → TopicSelectionorPreview → TopicSelection).
Exit
-
TopicSelection → Requestingon “select topic / open analysis”:- Client navigates to the result surface for
(sessionId, topic).
- Client navigates to the result surface for
-
TopicSelection → SessionExpiredif the backend TTL has elapsed and the session no longer exists.
Contract view
- There is no user account concept in C1.
- Entitlement is expressed as: “Is this
(sessionId, topic)entitled to full visibility?”
This aligns with the C1 overview and the stream semantics (paywall / unlock). See:
The result surface connects to the stream, requests analysis for (sessionId, topic), and renders content according to
stream_protocol.md.
Meaning
- Client has requested analysis for
(sessionId, topic)and is awaiting stream events.
Typical UI representation
- Result surface is mounted and begins connecting to the stream delivery channel.
Exit
Requesting → Loadingwhen the backend accepts the request and begins execution.- (In practice, the UI may move quickly to
Streamingonce the firstanalysisChunkarrives.)
- (In practice, the UI may move quickly to
Meaning
- Execution is in progress, but the client has not yet determined whether the visibility path will be
PrevieworFull.
How the path is determined
- The first content-bearing events determine the path:
- If the topic is free or already entitled, the client is effectively on the
Fullpath. - If the topic requires entitlement and is not yet entitled, the client is on the
Previewpath that terminates in apaywallevent.
- If the topic is free or already entitled, the client is effectively on the
Exit
Loading → Previewwhen policy requires upgrade / entitlement.Loading → Fullwhen policy allows full visibility.
Meaning
- Client shows a bounded preview of the analysis plus a call-to-action to unlock.
Stream semantics
- Two MVP-consistent ways to reach Preview:
- Live streaming (first-time paid topic):
receiveanalysisChunkup to a bounded fraction, then apaywallthat stops further content. - Cached non-stream (previously computed result available):
a truncatedresponsewithneedPayment=true, immediately followed by apaywall(no further content events for this request; see stream_protocol.md).
- Live streaming (first-time paid topic):
Exit
-
Preview → PaymentPendingon “click unlock”:- Client calls
POST /api/checkout(see rest_api_contract.md). - Browser is redirected to an external checkout page (payment provider).
- Client calls
-
Preview → TopicSelectionon “choose another topic”. -
Preview → SessionExpiredwhen backend TTL is exceeded and subsequent operations fail.
Meaning
- User is completing payment on the provider’s checkout page; the client is temporarily not in control of the screen.
Contract-level behavior
- Client initiates checkout via
POST /api/checkout. - The payment provider notifies the backend via
POST /api/webhook(not a client call). (See rest_api_contract.md.)
Exit
PaymentPending → Paidon successful payment completion and entitlement being recorded.PaymentPending → Previewon canceled/failed payment (no entitlement recorded).
Meaning
- Payment succeeded; the client is entitled to full visibility.
- If the full result is not yet materialized, the client may enter a “waiting for full result” view.
Stream semantics
unlockis emitted after entitlement is granted.- If
unlockcarriescontent, this is terminal success. - If
unlock(waiting=true)is emitted, the client should retry retrieval untilresponsearrives (see stream_protocol.md).
- If
Waiting is an informative UI hint; clients should treat unlock as terminal and rely on
session/stream retry semantics as defined in the stream protocol.
Meaning
- Backend has recorded entitlement for
(sessionId, topic).
Behavior
- Subsequent requests for the same
(sessionId, topic)should no longer produce a paywall. - After entitlement is granted, full-result delivery takes one of two forms:
- A terminal
response(cached/already-entitled or replay path). - A one-time
unlockcarrying full content for the user who just paid (post-paywall path; stream does not resume).
- A terminal
Meaning
- Client has access to the full result; content arrives as a single terminal event.
Stream semantics
- Backend emits a terminal
responsewithneedPayment=false(see stream_protocol.md).
Exit
Full → TopicSelectionon “choose another topic”.Full → SessionExpiredif the session TTL has elapsed and the session no longer exists.
Meaning
- Client receives incremental updates via
analysisChunkevents and renders them as they arrive.
Stream semantics
- A sequence of
analysisChunkevents arrives in order. - For a given streaming request, no further
analysisChunkevents are sent once one of the following occurs:- execution completes and the backend emits
analysisStatus(status="completed") - a
paywallis emitted (visibility is gated) - an
erroris emitted (see stream_protocol.md).
- execution completes and the backend emits
Exit
Streaming → Fullwhen execution completes and the client transitions to a stable “completed” view.Streaming → Previewwhen a terminalpaywallis received.Streaming → SessionExpiredif the session TTL has elapsed and the next operation fails.
Contract-level semantics
- Sessions have a finite TTL (implementation-defined).
- After TTL, REST inspection may return “not found or expired”, and streaming may return a terminal
error.
Meaning
SessionExpiredrepresents any state where the client holds asessionId, but the backend no longer recognizes it.
Reachability
TopicSelection → SessionExpiredPreview → SessionExpiredFull → SessionExpired
Typical UX
- Show an “expired session” message and guide the user back to the input surface to create a new session.
The diagram omits generic error paths for clarity. In the MVP:
- Any stream
errorevent moves the result surface into an error view. - Any REST error on session creation or checkout keeps the client in its current state with inline error UX.
These are implementation details and intentionally not modeled as separate primary states.
To review the C1 surface end-to-end:
- Use C1_client_server_contract.md for the narrative overview.
- Use rest_api_contract.md for REST endpoints and webhook behavior.
- Use stream_protocol.md for event types, termination rules, and replay behavior.
- Use this document plus ../diagrams/client_state_machine.mmd to map those contracts into a concrete client UX flow.