Conversation
… is live The session projector pushed `queue_update` only when a root Turn existed (`root && queueChanged(...)`), and `seedActive` returned no events at all for a snapshot without a root Turn. A queue that drained inside that window - the queued follow-up consumed as the Turn went terminal, or a drain between subscriptions - was therefore never projected: the renderer kept the queued card, and every retract of it failed with `not_found` (`queue.entry.retract`) while the failure path deliberately left the projection unchanged. Switching sessions back and forth did not clear it either, because the reseed hit the same gate. Project the authoritative queue whenever it changes, independent of a live root Turn (attributing the event to the previous root when the drain lands in the same frame the Turn disappears), and seed the queue mirror on subscribe even for an idle session - the empty queue is exactly what tells an observer to drop a stale card. The steering synthesis stays root-gated: it attributes messages to the live Turn. Fixes apache#5520 Generated-by: GLM-5.3-Flash (ZCode)
The Desktop session observer pins an empty seed event list for a snapshot
without a root Turn ("projects root lifecycle without fabricating content
events"), and the projector snapshots it seeds from can carry no queue at
all - the unconditional rootless seed both fabricated a queue_update the
contract forbids and crashed on the missing field.
The connected path is where the apache#5520 phantom actually lives: the drain
lands while the client is subscribed (session events route per session,
not per visible view), so projecting it there clears the card. A client
that never observed the session has no stale card for a seed to clear,
which is why seeding stays silent.
Fixes the regression introduced in 7b4a974.
Generated-by: GLM-5.3-Flash (ZCode)
|
The CI red was a real regression in my first push, thank you for catching it — diagnosed and fixed at What failed (3 tests, all Desktop-side consumers):
The fix: I reverted the seed hunk entirely ( Verified locally after the fix: |
Summary
Fixes #5520
A queue that drains while no root Turn is live was never projected: the session projector gated
queue_updateonroot && queueChanged(...), andseedActivereturned nothing for a rootless snapshot. The Desktop's queue mirror is driven only byqueue_update, so the renderer kept a phantom queued card whose 删除/retract failed withnot_foundforever — and switching sessions back and forth did not clear it, because the reseed hit the same gate. The Host broadcast the projection frames all along (#publishCanonicalfires on any canonical change); the drop was purely in the projector adapter.The fix stays at that shared root:
session-projector.tsnow pushesqueue_updatewhenever the queue changed, root or not, attributed toroot?.turnId ?? previousRoot?.turnId ?? ''. The steering synthesis stays root-gated (it attributes messages to the live Turn).seedActiveseeds the queue mirror for a rootless session too — the empty queue is exactly the signal that lets an observer clear a stale card on (re)subscribe.No consumer change is needed: the Desktop already deletes
messageQueueBySession[sessionId]when aqueue_updatecarries empty arrays, and the TUI mirrors the arrays verbatim.Verification
session-projector.test.ts(full file, rebuilt dist)message-coordinator.test.tsexecution-host-queue.test.tssession-subscription-client.test.tsupstream/mainworktree (Windows transportread side ended) — pre-existing, untouched by this PRnpm run check:asf-headersNot run locally: the full runtime-host matrix (~40 min; CI covers it) and the issue's Desktop manual repro (reported on macOS; the fixed root cause and its regression tests are platform-independent).
AI use
Analysis, patch, and tests done with GLM-5.3-Flash (ZCode) under human review.
Checklist
queue_update