Skip to content

fix(ui): retire duplicated host-accepted transient and keep tail rows on the reading measure - #5203

Open
UncertaintyDeterminesYou4ndMe wants to merge 2 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:fix/issue-5201-transient-duplicate-measure
Open

fix(ui): retire duplicated host-accepted transient and keep tail rows on the reading measure#5203
UncertaintyDeterminesYou4ndMe wants to merge 2 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:fix/issue-5201-transient-duplicate-measure

Conversation

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor

Fixes #5201

Problem (from the issue)

While a Turn is live, the durable local copy of the just-sent user message could render twice — once as the live Turn's own user bubble, once at the transcript tail with a Host accepted chip — and the tail copy ignored the transcript reading measure, stretching across the full window width while the identical component inside a Turn wrapped correctly.

Root cause

  • ChatView routes tail-turn transients into the live Turn only while the Turn has no user row yet (inlineTransientMessages). Everything "not inline" then rendered at the tail unconditionally — including a local copy the tail Turn already shows as its own user row.
  • Both sites that place <TransientUserMessage> directly under ChatMessageList (the transcript tail and the empty-session optimistic path) have no .maka-turn ancestor, and the reading measure (max-width: var(--maka-reading-measure) + centering, from fix(desktop): give the chat surface one reading measure #3997) is owned by .maka-turn.

Changes

  • transient-placement.ts (new): the placement rule as pure, testable logic. turnRendersUserMessage() detects a message id the transcript already renders as a Turn user row (user.id, or a user timeline item whose messageId matches — the same id equality the inline slot has always relied on); selectTailTransientMessages() excludes inline-routed and already-rendered rows from the tail slot.
  • chat-view.tsx: the tail slot uses the selector, and both tail/optimistic transient groups render inside a <section className="maka-turn"> wrapper — the same wrapper the live-status placeholder next to them already uses, so geometry and spacing stay uniform.

Deliberately preserved: a distinct message admitted into the live Turn while its prompt is already on screen (a queued second prompt) still renders at the tail — only true duplicates disappear.

Testing

  • New transient-placement.test.ts (9 cases): prompt-id match, admitted user timeline item match, non-user id reuse, cross-turn matching, empty turns, inline routing exclusion, distinct queued message retention, no-turn pending state.
  • packages/ui dist suite: 422/428 pass. The 6 failures are all mermaid-render-cache tests and reproduce identically on pristine main in this environment (verified by stashing this change and rebuilding) — pre-existing drift unrelated to this PR.
  • apps/desktop streaming-handoff suite (the placement assertions over rendered markup, including the exact data-transient-message-id count checks): 21/21 pass on this change. transient-message-projection (7/7), message-queue-ui-state (4/4), and app-shell-exact-turn-arm (2/2) also re-run green.

Adversarial review notes (pre-PR)

  • Hook-order/early-return audit: the new selector call is a plain const computed before all returns — no hooks involved.
  • Empty-state semantics kept: the conditional wrappers render false when empty, so ChatMessageList's "single null child" rule and the no-hero assertions are unaffected.
  • Scroll-anchoring suppression still targets the inner [data-transient-message-id] node; the wrapper adds no anchor-bearing content.
  • Checked every other TransientUserMessage render path (type-only imports in composer modules — untouched) and no CSS/e2e selector depends on the previous DOM shape of tail rows.

@github-actions github-actions Bot added the effort/M Under 500 readable lines label Sep 11, 2026

@me2seeks me2seeks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR 5203 Review

结论

APPROVE
把 tail transient 的去重规则抽成可测的纯函数,修复了 Host-accepted 消息重复渲染与 tail 行不受 reading measure 约束两个问题,语义与既有 inline slot 完全一致。

… on the reading measure

- A durable local copy whose message id the transcript already renders as a
  Turn user row (primary prompt or admitted user timeline item) no longer
  renders a second time below the running status; the inline slot dropped it
  but the tail slot drew everything 'not inline', so the same text showed
  twice while a Turn was live
- Tail and empty-session transient rows render inside a .maka-turn wrapper:
  the reading measure (max-width + centering) lives on .maka-turn, so rows
  placed directly under ChatMessageList stretched across the full window
  width while the identical component inside a Turn wrapped correctly
- Extract the placement rule into transient-placement.ts with regression
  tests covering prompt-id matches, admitted user timeline items, distinct
  queued messages, and inline routing

Fixes apache#5201
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (d2e1be5db) to resolve conflicts with #5217's per-turn inline routing: the tail slot now complements the inlineTransientMessagesByTurn map (drops anything already routed inline or already rendered as a Turn user row) and keeps the .maka-turn reading-measure wrapper for tail/optimistic rows. Re-verified after the rebase: @maka/ui dist suite 440/440, streaming-handoff 21/21 (exact data-transient-message-id counts included), transient-message-projection 9/9.

@UncertaintyDeterminesYou4ndMe
UncertaintyDeterminesYou4ndMe force-pushed the fix/issue-5201-transient-duplicate-measure branch from 188f899 to ea5d565 Compare September 12, 2026 15:39
…he third enqueue; not touched by this diff)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/M Under 500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(ui): host-accepted transient user message duplicates the live turn's prompt and renders across the full window width

2 participants