Review follow-up on the switch-back duplicate-reply fix.
- appendLiveSessionProjection's committed-turn gate matched by text only:
the latest persisted prompt equal to inflight.user, followed by a
committed reply that extends inflight.assistant. When the same prompt
is sent again and the transcript lacks the new prompt row (cached-
transcript path after a send from another client, or a runtime history
that has not committed the running turn), the PREVIOUS turn's answer
satisfies that match, and the next turn's live stream was dropped. The
gate now also requires the committed reply's timestamp to be at or
after the snapshot's turn_started_at; with either value missing it
projects as before (a duplicate is recoverable, a lost stream is not).
- overlayConcurrentMessageChanges dropped a settled stream row whenever
the page's latest turn held a committed row with the same text. It now
counts only committed rows the page newly added (not in the warm-cache
baseline, so never an earlier turn's identical answer), and never drops
a row that carries an error, which the committed text cannot show.
Tests: one vitest invariant (resent prompt keeps its projection and its
settled row; errored settled row survives). Red on the PR head; each of
the two overlay guards reverted alone turns it red again.