Remote / Server-derived state
Mutations and live data
A mutation flows through the ordinary Foldkit path — UI Message → update →
Data.mutate → Command — and returns to the Model as a RemoteMessage:
sequenceDiagram
participant UI
participant update
participant Data
participant Server as server
UI->>update: ClickedRename
update->>Data: Data.mutate
Data->>Server: Command (RPC)
Server-->>update: MutationSucceeded { requestId, entities, now }
update->>Data: Data.reduceData.mutate applies MutationStarted to the Model (the request id comes from
the Model's own sequence, so update stays pure) and returns the Command whose
Message settles it. That Message carries the normalized entity patches and
now, the clock reading that dates what they write; the typed Output is what
Remote.mutateInto returns. Settling is idempotent per requestId, so a transport retry
cannot apply the same change twice. Remote.mutateInto is the one-step
imperative form for SSR and tests.
Optimistic changes belong to the mutation: MutationStarted carries its entity
patches and connection changes (ConnectionChange.prepend/append/remove), and
success or failure releases them together by request id. Patches are ordered
layers over the base store, not inverse patches: the visible store is
recomputed as base + layers, so overlapping layers rebase for free. Connection
changes are overlays outside the server-known region; a result's confirmed
connections take the place of the request's own, so a temporary edge becomes
the real one in place.
On the server, RemoteServer.liveHub turns "these fields of this entity
changed" into the right patch for each subscriber: it re-reads the changed
fields a subscriber selects through the entity's own source, under that
subscriber's principal, so authorization and computed fields take the normal
path and a subscriber selecting none of the fields costs nothing.
Live data is an Effect streaming RPC. Each stream has a monotonic cursor: duplicates are ignored, and an event ahead of the cursor is a gap — it is not applied, and the stream is recorded so the host can resync rather than silently miss facts. Entity events update the store; connection events change membership and ordering. A deleted entity is a tombstone that every connection listing it skips, a live removal hides a known edge until a fresh page brings it back, and a merged page prunes the settled overlays it supersedes.