Files
amethyst/quartz
Claude f789fe4f53 refactor(quartz): EventStoreProjection becomes pure state machine; ObservableEventStore.project returns cold Flow
Removes scope ownership from EventStoreProjection. The class is now a
pure state machine — no Job, no AutoCloseable, no embedded
StateFlow:

  class EventStoreProjection(store, filters, nowProvider) {
      suspend fun seed()                         // run the seed query
      fun apply(change: StoreChange): Boolean    // apply one mutation
      fun snapshot(): ProjectionState.Loaded     // current view
  }

ObservableEventStore.observe(filter, scope) is renamed to
ObservableEventStore.project(filter) and now returns a cold
Flow<ProjectionState<T>>. Each collection allocates its own state
machine, runs its own seed, and unsubscribes when the collector
cancels. The flow is built using channelFlow + the state machine —
the class stays as the load-bearing primitive.

Standard caller pattern:

  val state = observable.project<TextNoteEvent>(filter)
      .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000),
               ProjectionState.Loading)

Tradeoffs:
- Cold flow → multiple collectors each re-seed. ViewModel pattern
  with stateIn shares one collection across observers (the standard
  Android approach).
- close() goes away. Cancelling the collecting scope tears down the
  projection.
- closeStopsListening test renamed to cancellingScopeStopsListening
  and now verifies the StateFlow's value freezes after its scope is
  cancelled.

17/17 projection + 240/240 store tests pass.

https://claude.ai/code/session_01Jny85MTu1ynKgFBgysfWu5
2026-04-30 15:35:30 +00:00
..