Phase 2 T44 deferred interjection regenerate — when the original turn
group included a follow-on interjection beat we left it untouched. Now
regenerate redoes BOTH halves:
- Detect a sibling interjection by looking up assistant_turn events
pinned to the same user_turn_id with `interjection_of` set.
- After streaming the new primary, run `detect_interjection` against
the new primary text.
- If True: stream a new interjection from the silent witness, append
with `interjection_of=<new primary speaker_id>`, supersede the
original interjection, and re-run memory + state-update for the new
beat.
- If False: supersede the original interjection without a replacement
(back-pointer goes to the new primary so the row stays consistently
hidden).
Also broadcast a `turn_html_replace` event for the new interjection so
the front-end can swap the prior interjection node in place (mirrors
T73.1's primary swap).
Tests:
- `test_regenerate_with_interjection_redoes_both_turns`: classifier
returns True; assert two new assistant_turns land for the same
user_turn, second carries `interjection_of`, originals superseded.
- `test_regenerate_drops_interjection_when_classifier_returns_false`:
classifier returns False; assert one new assistant_turn (primary
only) and the original interjection is superseded with no
replacement.
`interjection_of` carries the primary's *speaker_id* (matching the
existing convention in chat/web/turns.py) rather than the event_id.
After the new assistant_turn lands, publish a `turn_html_replace` SSE
event carrying the rendered HTML, the new turn_id, and the original
assistant_turn id as `supersedes_id` so connected tabs can swap the
prior DOM node in-place. Phase 1 T29 deferred this — page had to refresh
to see the regenerated turn.
Uses a new event name (not the existing `turn_html`) because the HTMX
`sse-swap="turn_html"` consumer expects raw HTML and an *append*
semantic; regenerate is a *replace*. The new event ships as JSON
(supersedes_id forces sse.py's JSON branch) so the front-end JS can
read the swap target from the payload.
Test: `test_regenerate_broadcasts_turn_html_over_sse` patches the
`publish` reference inside the regenerate module and asserts the
event shape.