refactor(tui-03): remove ConnectSession from SessionStarted, keep on SessionSnapshot

SessionSnapshot is the correct trigger for connectSession() — the client
has enough state to stream live events at that point. SessionStarted fires
pre-snapshot and no longer needs to emit ConnectSession.
This commit is contained in:
2026-05-25 12:09:03 +04:00
parent d6df38418a
commit de069dbf6a
5 changed files with 20 additions and 7 deletions
@@ -16,10 +16,10 @@ object RootReducer {
val phase = SnapshotPhaseReducer.process(state, action)
var current = phase.state
val allEffects = mutableListOf<Effect>()
val isReplay = action is Action.ServerEventReceived &&
val replayingBufferedEvents = action is Action.ServerEventReceived &&
action.message is ServerMessage.SnapshotComplete
for (a in phase.actionsToDispatch) {
if (isReplay) {
if (replayingBufferedEvents) {
val inner = SnapshotPhaseReducer.process(current, a)
current = inner.state
for (ia in inner.actionsToDispatch) {
@@ -337,7 +337,7 @@ object SessionsReducer {
msg: ServerMessage.SessionStarted,
clock: () -> Long,
sessions: SessionsState,
): Pair<SessionsState, List<Effect.ConnectSession>> {
): Pair<SessionsState, List<Effect>> {
val summary = SessionSummary(
id = msg.sessionId.value,
status = "ACTIVE",
@@ -348,8 +348,7 @@ object SessionsReducer {
lastOutput = null,
)
val selected = sessions.selectedId ?: msg.sessionId.value
return sessions.copy(sessions = sessions.sessions + summary, selectedId = selected) to
listOf(Effect.ConnectSession(msg.sessionId))
return sessions.copy(sessions = sessions.sessions + summary, selectedId = selected) to emptyList()
}
private fun processInferenceCompletedMessage(
@@ -77,6 +77,7 @@ object SnapshotPhaseReducer {
is ServerMessage.ToolFailed -> msg.sessionId.value
is ServerMessage.ToolRejected -> msg.sessionId.value
is ServerMessage.ApprovalRequired -> msg.sessionId.value
is ServerMessage.SessionSnapshot -> msg.sessionId.value
else -> null
}
@@ -118,6 +118,20 @@ class RootReducerTest {
assertEquals(state.sessions.sessions, newState.sessions.sessions)
}
@Test
fun `SnapshotComplete with empty pendingEvents flips phase and produces no sessions`() {
val initial = TuiState(snapshotPhase = true, pendingEvents = emptyList())
val (result, effects) = RootReducer.reduce(
initial,
Action.ServerEventReceived(com.correx.apps.server.protocol.ServerMessage.SnapshotComplete),
fixedClock,
)
assertEquals(false, result.snapshotPhase)
assertTrue(result.pendingEvents.isEmpty())
assertTrue(result.sessions.sessions.isEmpty())
assertTrue(effects.isEmpty())
}
@Test
fun `AppendChar followed by Backspace returns to original buffer`() {
val initial = TuiState(inputMode = InputMode.ROUTER, inputBuffer = "ab")
@@ -96,8 +96,7 @@ class SessionsReducerTest {
assertEquals(1, state.sessions.size)
assertEquals("s1", state.sessions[0].id)
assertEquals("s1", state.selectedId)
assertEquals(1, effects.size)
assertTrue(effects[0] is Effect.ConnectSession)
assertTrue(effects.isEmpty())
}
@Test