Skip to content

webui(chat): match echoed user messages whitespace-tolerantly (fix duplicate)#403

Merged
edwin-zvs merged 2 commits into
mainfrom
webui-chat-dedup
Jun 13, 2026
Merged

webui(chat): match echoed user messages whitespace-tolerantly (fix duplicate)#403
edwin-zvs merged 2 commits into
mainfrom
webui-chat-dedup

Conversation

@edwin-zvs

Copy link
Copy Markdown
Contributor

Problem. In chat mode, a sent message sometimes appears twice — intermittently, and more on some harnesses (your hunch was right).

Cause. The webui shows an optimistic row when you send, then reconciles it against the harness's echoed user message event so it isn't drawn again. But the reconcile matched by exact text (entry.text === text), and harnesses don't echo byte-for-byte:

  • codex stores the text and replays it through its rollout JSONL,
  • smith / claude re-emit it,
  • any of them can add a trailing newline, CRLF, or reflow the line wrapping.

So the exact match misses → the optimistic row and the echo both render.

Fix. Reconcile with a whitespace-tolerant match in consumeOptimisticMessage:

  1. exact, then
  2. whitespace-normalized (\s+ → single space, trimmed — handles trailing \n, CRLF, reflow, multi-line), then
  3. one normalized text wrapping the other (a harness may prepend/append a little),

picking the oldest match so rapid sends pair in send order. Dissimilar user events (e.g. injected OBSERVATION: messages on the orchestrator) don't match, so they still render normally.

Verification. The webui JS is embedded (the build doesn't lint it), so the matching logic was checked with a standalone node harness — 9/9 cases pass: exact, trailing newline, CRLF, reflow, trim, appended-suffix (prefix), OBSERVATION:-not-consumed, ordered pairing of two pending, empty list. Daemon builds clean.

Best confirmed live after the new constructd is running (the webui asset is embedded in it) — send a multi-line / trailing-whitespace message on codex and smith and confirm a single row.

In chat mode, a sent message sometimes rendered twice. The optimistic row is
reconciled against the harness's echoed user `message` event by EXACT text
(`entry.text === text`). But harnesses don't echo byte-for-byte: codex stores
then replays the text via its rollout, smith/claude re-emit it, and any of them
can add a trailing newline, CRLF, or reflow the wrapping. The exact match then
misses, so both the optimistic row and the echo show.

Match exact → whitespace-normalized (`\s+`→space, trimmed) → one normalized
text wrapping the other, picking the oldest match so rapid sends pair in send
order. Dissimilar user events (e.g. injected `OBSERVATION:` messages) don't
match, so they still render.

Matching logic verified with a standalone node check (exact, trailing newline,
CRLF, reflow, trim, appended suffix, observation-not-consumed, ordered pairing,
empty list).
This render-perf test asserts an 80ms budget tuned for local dev; on the shared
2-core GitHub runner it intermittently exceeds it (it failed the unrelated
webui-dedup run, passes locally in 0.13s). Add it to the existing pty_render
perf-test skip list — same rationale as the other four; still runs locally.
@edwin-zvs
edwin-zvs merged commit 55ef3b8 into main Jun 13, 2026
1 check passed
@edwin-zvs
edwin-zvs deleted the webui-chat-dedup branch June 13, 2026 19:02
@edwin-zvs edwin-zvs mentioned this pull request Jun 16, 2026
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.

1 participant