fix(core,web): show newest messages instead of oldest on hydration#1532
Conversation
…oleam00#1531) Two defects combine to silently lose recent messages in long conversations: 1. listMessages() returns the oldest N messages (ORDER BY ASC LIMIT 200). In conversations with >200 messages, the newest messages disappear from the UI after refresh — the exact opposite of every chat UI's expectation. 2. The stuck-placeholder recovery path in ChatInterface replaces React message state with the server fetch, dropping any live SSE-only messages (not just system messages) that haven't been persisted yet. This causes mid-session message loss without any user-visible trigger. Fix: - Change listMessages() to ORDER BY DESC then reverse, so the newest N messages are returned in chronological order - Broaden stuck-placeholder recovery to merge by message ID instead of only preserving system messages — any client-only message (SSE-streamed, system status) not in the hydrated set is kept and interleaved by timestamp Closes coleam00#1531
📝 WalkthroughWalkthroughDB query now fetches newest messages (ORDER BY created_at DESC) and reverses them for chronological display; a test was updated accordingly. The web UI's stuck-placeholder recovery now preserves meaningful client-only messages (not just system messages) and interleaves them into the hydrated DB results. ChangesMessage Ordering and Merge Recovery
Sequence Diagram(s)sequenceDiagram
autonumber
participant UI as Web UI (ChatInterface)
participant API as Server API (/messages)
participant DB as Database
UI->>API: GET /api/conversations/:id/messages (limit=200)
API->>DB: SELECT ... ORDER BY created_at DESC LIMIT $2
DB-->>API: newest 200 rows (newest-first)
API-->>UI: returns rows (newest-first)
UI->>UI: reverse rows -> hydrated (oldest-first)
UI->>UI: compute clientOnly (SSE/live messages not in hydrated)
UI->>UI: interleave clientOnly by timestamp into hydrated
UI-->>User: merged message list (chronological, includes live messages)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/components/chat/ChatInterface.tsx`:
- Around line 453-455: The recovery merge currently keeps all client-only
messages (clientOnly from prev filtered by hydratedIds), which preserves
transient optimistic placeholders and SSE-only "thinking" entries; change the
merge logic where clientOnly is computed to further filter out
non-persistent/transient items—only retain messages that are meaningful and
meant to persist (e.g., have a persistent identifier/DB id property, are not
marked isOptimistic, are not SSE-only/system placeholders, and have non-empty
content). Apply the same tightened filtering in the related recovery branch that
handles the other merge (the block referenced around lines 458-462) so only
durable messages are merged into hydrated.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 39420a21-d0e3-4f0f-8fa4-4c4314948f9e
📒 Files selected for processing (3)
packages/core/src/db/messages.test.tspackages/core/src/db/messages.tspackages/web/src/components/chat/ChatInterface.tsx
Address CodeRabbit review: tighten the client-only filter to exclude optimistic user rows and empty thinking placeholders. Only preserve system messages and assistant messages with meaningful content (content, error, workflowDispatch, workflowResult, or toolCalls).
|
@kagura-agent related to #1531 — message ordering fix. |
|
@kagura-agent related to #1173 — message ordering fix may affect workflows page behavior. |
|
Hi @kagura-agent — thanks for opening this PR. This repository uses a PR template at
Could you fill those out (even briefly)? The template If a section genuinely doesn't apply, just write "N/A" in |
|
Done — filled out all the template sections (UX Journey, Architecture Diagram, Label Snapshot, Change Metadata, etc.). Thanks for pointing that out, @Wirasm! |
|
Hey @Wirasm 👋 just checking in — template sections are all filled out now. Is there anything else I should adjust, or does this look good to move forward? Happy to make changes! |
Review SummaryVerdict: ready-to-merge This PR correctly fixes the chat history ordering bug by fetching newest messages from the DB (DESC + compensating reverse) and broadening client-side SSE recovery merge to preserve all meaningful client-only messages, not just system messages. Code is clean, test is correct, and CLAUDE.md is fully compliant. Blocking issuesNone. Suggested fixesNone. Minor / nice-to-have
Compliments
Reviewed via maintainer-review-pr workflow (Pi/Minimax). Aspects run: code-review, test-coverage, comment-quality. |
…oleam00#1532) * fix(core,web): show newest messages instead of oldest on hydration (coleam00#1531) Two defects combine to silently lose recent messages in long conversations: 1. listMessages() returns the oldest N messages (ORDER BY ASC LIMIT 200). In conversations with >200 messages, the newest messages disappear from the UI after refresh — the exact opposite of every chat UI's expectation. 2. The stuck-placeholder recovery path in ChatInterface replaces React message state with the server fetch, dropping any live SSE-only messages (not just system messages) that haven't been persisted yet. This causes mid-session message loss without any user-visible trigger. Fix: - Change listMessages() to ORDER BY DESC then reverse, so the newest N messages are returned in chronological order - Broaden stuck-placeholder recovery to merge by message ID instead of only preserving system messages — any client-only message (SSE-streamed, system status) not in the hydrated set is kept and interleaved by timestamp Closes coleam00#1531 * fix: filter transient placeholders from stuck-placeholder recovery merge Address CodeRabbit review: tighten the client-only filter to exclude optimistic user rows and empty thinking placeholders. Only preserve system messages and assistant messages with meaningful content (content, error, workflowDispatch, workflowResult, or toolCalls).
Summary
limit=200default, API max500, cursor-based pagination (future work), and DB schema are all unchangedUX Journey
Before
After
Architecture Diagram
Before
After
Connection inventory:
Label Snapshot
risk: lowsize: Score,webcore:messages,web:chatChange Metadata
bugmultiLinked Issue
Validation Evidence (required)
bun run validatenot run locally; CI covers remaining checksSecurity Impact (required)
NoNoNoNoCompatibility / Migration
Yes— public contract ("returns messages in chronological order") is preservedNoNoHuman Verification (required)
Side Effects / Blast Radius (required)
listMessages()consumers now get newest-N instead of oldest-N; stuck-placeholder recovery is more permissivelistMessages()would need updatingRollback Plan (required)
Risks and Mitigations
listMessages()returns oldest-N