Skip to content

fix(transcript): preserve message order when HTTP is ahead of live events - #1294

Merged
pascalandr merged 1 commit into
devfrom
fix/transcript-message-order
Nov 5, 2026
Merged

pascalandr merged 1 commit into
devfrom
fix/transcript-message-order

Conversation

@pascalandr

Copy link
Copy Markdown
Contributor

Summary

Fixes #1293

Keep later assistant steps after the user prompt they answer when a native HTTP message page gets ahead of SSE delivery.

Root cause and reproduction

projectOpenCodeMessages projects the live SDK reducer through seedSessionMessagesV2 into hydrateMessages. The native HTTP page may already include [user, first assistant, later assistant] while the reducer still holds [user, first assistant]. Its next event previously prepended every omitted resident, producing [later assistant, user, first assistant], matching the report's screenshot.

The new deterministic session-api-running-open.test.ts regression uses the real handleInstanceInvalidation dispatcher and loadMessages, with only native API responses stubbed. It fails on clean origin/dev at the order assertion after the first assistant's final live event, before applying this fix. No sleeps, retries, clock skew, pagination boundary, worktree move or reconnect are needed. IDs and timestamps deliberately disagree with sequence order to prevent introducing either as a sort key.

Change

  • Preserve omitted resident runs after their preceding shared row; keep leading history before the snapshot.
  • Retain native incoming order for shared rows and existing pending optimistic sends at the tail.
  • Keep authoritative page replacement, contents, request fences and bounded window behavior unchanged.
  • Document the shared-store ordering contract. No dependencies, extra reads or service changes.

The correction is in the common UI store used by Electron, Tauri and web. The Windows tests do not claim native desktop-host qualification. Public help steps, labels, controls and configuration remain unchanged.

Validation

  • Red before fix: the new dispatcher/load regression returns msg_b_later, msg_z_user, msg_a_first instead of msg_z_user, msg_a_first, msg_b_later.
  • 174/174 tests pass across instance-store, message-window/hydration authority, running-session opening, native data projection/read merging/trailing reads/reconnect/idle, session actions and pagination.
  • npm run typecheck --workspace @codenomad/ui passes.
  • 3/3 Chromium tests pass in session-rendering.test.ts: optimistic-send ordering and return, sends from a long transcript, queued delivery after a history rewrite.
  • git diff --check passes.
  • Own registered worktree and independent npm ci --prefer-offline --no-audit --no-fund; no shared-daemon access or modification and no changes to other worktrees.

Touched oversized source: packages/ui/src/stores/message-v2/instance-store.ts (approximately 1,531 lines).

…ents

A native message page can contain later assistant steps before their SSE events
reach the renderer. The live reducer then projects a smaller, overlapping set
of rows into the already loaded transcript. Treating every omitted resident as
older history prepended those later steps ahead of the first user prompt and
its initial assistant response while the session was still working.

Keep each omitted resident run after its preceding shared row, retaining the
incoming native order for shared rows and the existing optimistic-send tail.
Leading history and disjoint live appends retain their established placement;
authoritative page replacement, pending-send confirmation, message contents and
window membership limits keep their existing contracts. No timestamp or ID
sorting, extra reads, service changes or new dependencies are needed. The shared
UI store applies the correction equally to Electron, Tauri and web clients.

Add a deterministic regression using the real native event dispatcher and
loadMessages path: an HTTP page with a later step is followed by the first
step's final live event. It fails against origin/dev by producing later, user,
first instead of user, first, later, then passes with the fix. Cover omitted
prefixes, interior and trailing runs, disjoint appends, incoming delivery order,
pending sends, repeated projections and authoritative replacements in store
tests. Document the ordering contract in the transcript agent conventions.

Validation: 174 focused store/reducer/action/pagination tests pass; UI typecheck
passes; three real Chromium SessionView regressions pass for optimistic send
reordering, long-transcript sends and queued delivery after a history rewrite.
The shared OpenCode daemon was not accessed or stopped. Public help usage and
controls are unchanged.

Fixes #1293

@pascalandr pascalandr left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: PASS — P0: 0 · P1: 0 · P2: 0 · P3: 0.

Reviewed exact head 7dd71d9e546f0fc4327f2040e85844755bcedbc5 against dev. No reproduced defects found.

Root cause verified

Read #1293 and its comments and inspected the screenshot. The reported ordering is reproducible through the real native dispatcher, loadMessages, shared message store, and Solid SessionView.

  • With the origin/dev store implementation (4bac37339fb79ce63cd535ec7cd3bf6e568f716c), the new regression fails at session-api-running-open.test.ts:73: [later assistant, user, first assistant].
  • At the reviewed head it passes: [user, first assistant, later assistant].
  • Independently reproduced both orders in Chromium using the existing real SessionView fixture. Baseline source substitution was in memory; no checkout edits or dependency sharing were used.

Merge and adjacent paths

Reviewed instance-store.ts:607–634 and every hydration/projection caller. The merge visits resident and incoming IDs linearly, preserves each omitted run after its preceding shared row, retains leading/disjoint residents, follows incoming shared-row order, and keeps omitted pending sends at the tail. It introduces no ID/timestamp sorting. Authoritative omission removal, empty snapshots, cancellation/revert handling, compaction, history-window isolation, pagination cursors, and same-ID optimistic confirmation retain their existing authority paths.

An additional read-only check passed 15,876 combinations against the real store: multiple omitted runs, head/tail omissions, disjoint inputs, incoming reorderings, repeated projections, authoritative deletions, pending identity confirmation, and exact membership without duplicates.

Validation

  • Primary touched/adjacent unit run: 310/310 passed (projection, reconnect, settlement, replay, pruning/pagination, request authority, message windows, optimistic sends, and running-session opening).
  • Expanded adjacent unit run: 124/124 passed, with overlap with the first run (all message-v2 suites, rehydration/eviction, send lifecycle, native events, navigation and timeline projections).
  • Chromium: 154/154 passed across session-rendering, history-navigation, history, session-search, system-messages, timeline-sync, and transcript-presets.
  • UI typecheck and git diff --check passed.
  • Added regressions exercise production logic, with no added sleeps, retries, or timeout increases; browser checks mount real components.

The author worktree was clean at the exact head before and after validation. Its own root dependency directory and npm ls --workspace @codenomad/ui --depth=0 passed preflight. Validation covers Windows Node/Chromium; installed Electron/Tauri hosts were not exercised.

@pascalandr
pascalandr merged commit 0240f39 into dev Nov 5, 2026
17 of 18 checks passed
@pascalandr
pascalandr deleted the fix/transcript-message-order branch November 5, 2026 09:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: message order is wrong

1 participant