Sync / Replicated state

2. The client

Open storage and a replica, mount the application over it, start the loop:

ts
import { Effect, Scope } from 'effect'
import { ReplicaId, Sync } from 'foldkit-sync'

const container = document.getElementById('app')!
const scope = Effect.runSync(Scope.make())
const storage = Effect.runSync(
  Effect.provideService(Sync.indexedDb('todos/tab-1'), Scope.Scope, scope),
)
const replica = Effect.runSync(TodoSync.openReplica(ReplicaId.make('tab-1'), storage))

const mounted = Sync.mount(App, TodoSync, {
  replica,
  container,
  view: (model, h) => ({
    title: 'Todos',
    body: h.ul([], model.todos.map(todo => h.li([], [todo.title]))),
  }),
  onPersistenceFailure: (model, error) =>
    modifyFields(model, { lastError: () => Option.some(error.message) }),
})

Effect.runFork(
  Effect.provide(replica.start, Sync.transport.socket({ url: 'wss://example.com/sync' })),
)

mount runs the ordinary Foldkit runtime. A durable Message applies through update at once and a Command then saves it to the outbox; a local Message is untouched. replica.start exchanges with the server now, after every edit, and whenever the server says something changed, retrying failures on a backoff. When an exchange moves the replica (a commit, a rejection, a checkpoint), the mount installs the reconciled slice back into the Model, outside update.

The Sync README walks each call; runtime binding explains the mount's internals.