🔭 feat: Conversation Trace Viewer - #15869
Conversation
Adds a trace view for a user's own conversations: a summary, a pinned waterfall overview with interval focus, a hierarchical record ledger and a record inspector, opened from the chat header (or the mobile overflow menu) and read through a provider-neutral seam with Langfuse as the first reader. - interface.traceViewer (enabled, showInputOutput, maxRecords, maxContentLength, requestsPerMinute), off by default - /api/traces/:conversationId availability, records and record routes, re-proving conversation ownership and narrowing the Langfuse session to traces the user's own sampled responses produced - Langfuse reader over the v2 Observations API with cursor paging, time bounds, typed failures and server-side input/output withholding - forks no longer copy the source messages' trace sampling record
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b921060d93
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- interface.traceViewer.requestTimeoutMs bounds each Langfuse round trip - availability is one existence query across every sampled response instead of the oldest one, run alongside the ownership check - tree items report their position among visible siblings - point-in-time Langfuse events read as completed, not running - reads prefer the project holding the most responses when a tenant connection arrived mid-conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f9a478e354
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- page cursors and record details stay on the project that served the first page; pages report an opaque sourceId - Open in Langfuse only appears when the linked project served every loaded page (the session link now reports its destinationId) - client and server abort Langfuse reads when the viewer closes or the client disconnects - reads never wait on the central project lookup, which the request timeout does not govern - the cost total is withheld when any model call with usage is unpriced
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 546f696d09
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…Refresh Failures - a settled run invalidates the cached trace pages, so reopening shows the new turn instead of pages fresh for another 30 seconds - trace durations and clock times format in the app language and the user's 12/24-hour setting, as message timestamps do - a failed refresh with every page loaded surfaces an alert and a retry
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 793f137d04
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- the message-create route and imports strip langfuseSampled and langfuseDestinationIds, and trace reference queries ignore client-authored rows (isUserSubmitted) and user turns, so a forged row cannot claim a trace - the Langfuse reader receives its destination resolver and HTTP client from the route instead of defaulting to process-global ones - availability asks the client to check again while central's project id is still resolving, instead of caching a false answer - listRecords tries the next readable project when legacy responses left the first one empty - a settled run also invalidates cached record details - running is read from a record's status, not a missing end time
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1565792b55
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…g a Turn - ownership and both trace reference queries carry the requester's tenant (tenantless is its own scope), so a duplicate user and conversation id in another tenant cannot grant or supply trace references - a read visits, page by page, every project that could hold a turn no earlier project provably holds, so legacy turns in another project stay reachable after a newer destination ranks first - the trace read limiter takes its store from the route - transient availability failures retry instead of hiding the control
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6b8b98af94
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…abels - a fresh read fails over to another project holding the same turns when the preferred one fails; the cursor carries the failed sources so later pages keep the rebuilt plan, and detail reads fail over the same way - destinations for one project collapse into one source - the trace read limiter keys each user within their tenant - refresh and a settled run keep only the newest page before refetching, so older pages are not replayed through the limiter; retry repeats the read that failed - search matches the localized kind and status labels a row shows
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 490faa8487
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…cts, Fit Trimmed Views Rank trace sources by the newest sampled response they could hold before coverage and preference, so a conversation that moved projects shows its latest turn first. Throw a list failure when a failed project could hold a turn that no answering project provably holds, instead of rendering an empty trace. Detail reads keep probing projects after an empty answer, skip ones whose turns an answering project provably holds, and surface a failure that could hide the record. Refresh and settled runs trim the cache to the newest page; the viewer now fits a focused interval to the remaining records or clears it, and drops a selection whose record left with the older pages.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3a3ceaa037
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…Mounted List reads now page by turn segments instead of whole projects. A segment is the newest unread turn and up to 49 older consecutive turns one project could hold. The request filters Langfuse to those turns' run and title traces, so pages stay newest first when a conversation's responses alternate between projects. Legacy turns without destination ids are asked of the other projects in the segment. A project that failed for newer turns is retried when it alone holds older turns. A page whose rows all fail the observation schema is an upstream failure, not an empty trace. A failed turn's error row now stores the sampling fields of the run that failed, plus langfuseRunId when that run was created, so the trace viewer and feedback scores follow the run's trace. langfuseRunId is stripped from client message writes and imports, dropped by forks, and hidden from client message projections. The ledger keeps its active tree item mounted while virtualization scrolls it out of the window, so aria-activedescendant always names a rendered row.
There was a problem hiding this comment.
💡 Codex Review
https://github.com/danny-avila/LibreChat/blob/c9f6d8c64cd9926156b99bdf403837959f8055ac/api/server/controllers/agents/responses.js#L422
Record trace provenance on successful chat turns
The only successful-response call to getLangfuseTraceMessageFields is here in the OpenAI Responses API controller. In the production web-chat path checked in ResumableAgentController, the terminal response is saved from response without this helper, and a repository-wide search finds no other success-path writer for langfuseSampled; therefore ordinary successful Agent chats never satisfy hasSampledTraceMessage, so the new header control remains hidden unless the conversation also contains a marked failed or Responses-API turn. Add the sampling fields to the standard Agent response persistence paths as well.
AGENTS.md reference: AGENTS.md:L31-L34
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ontent
A segment that reads more than one project now runs every read before returning. A read that fills the record budget cuts the page at the oldest start time all reads loaded, and the continuation carries that start time as an inclusive bound instead of a Langfuse cursor. Every project continues from the same point, so a newer turn held by another project is never left behind an older one, and failover works on any page. A page drawn from several projects no longer names a single source. A continuation that would repeat its own bound fails instead of looping.
Input and output that are literally the strings {} or null are kept; only an empty object or JSON null value is dropped.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a8460061df
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Trace ids derive from message ids, which a request can influence, so a colliding id must not return someone else's trace. Every Langfuse list, probe and detail read now also filters by userId: the requester's internal id, plus the configured langfuse.trace.userIdField value when set. The server stamps that id on the trace at export. The handler passes the requesting user into the trace query. When a turn's run and title run sit in different projects, the probes' root start times decide which is read first, so a title generated after the response is not left behind Load older. Cursors name the trace a segment starts at within its turn. The failed-turn trace lookup moves into packages/api as getFailedTurnTraceFields. The agent controller only passes the error row id, the run id and whether the run was created.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c035b5053a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A configured langfuse.trace.userIdField can export a non-unique value (name, username) or one that repeats across tenants (email, provider ids) as the trace userId, so it cannot prove who a trace belongs to. Reads now require the internal user id alone. A deployment that exports another field reports no trace and logs why once, instead of trusting that value. A failed read of a title run that a probe found in that project now raises its failure instead of dropping the title. The page contract now states that pages are ordered by turn and a turn's records may continue on the next page.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 86b30588b3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
List reads load sampled response references a segment at a time: up to 50 ending at the cursor's turn, plus one older response that names the next page. Pages of a long conversation no longer rematerialize every sampled response. getConversationTraceRefs gains through and limit, anchored by createdAt and _id. The message-create route and conversation imports strip trace sampling fields through withoutTraceRefs instead of their own field lists. A userIdField the exporter ignores counts as the internal id. A root lookup whose cursor repeats fails that project instead of paging forever. In the viewer, Refresh also rereads the open record's detail, and closing the inspector after a filter removed every row returns focus to the search field.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c4dd606429
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…to Their Turn Cursors carry the anchor response's order key (creation time, then _id). The bounded sampled-response read then runs alongside the first-message lookup, with no anchor lookup ahead of it. A key that no longer names its response returns nothing, and the reader answers the cursor as changed. Detail reads take the turn the list attributed the record to (?message=). They load only that sampled response instead of the conversation's whole history, and ask Langfuse within that turn's traces. The route rejects a detail read without it, and the client sends it from the listed record. A list read whose Langfuse cursor repeats now fails instead of handing the next page the same records.
…deepseek-trace-viewer-ca115a # Conflicts: # api/server/controllers/agents/request.js
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c5175da16a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2c825ae7b5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…Copies Unsampled A page cursor whose base-36 time parses to a number outside the Date range reached Mongoose as an Invalid Date and surfaced as a 500 the viewer retried; it now resumes nothing, which the reader reports as invalid_request so the viewer reloads its newest page. The detail read capped the message id at the Langfuse record-id bound while the app stores response ids at any length, so a listed turn could refuse to open; message ids only name a stored row and are accepted at any length. Ids that reach a Langfuse filter keep their caps. Copied and client-authored rows now carry langfuseSampled: false instead of no record, so a feedback score on a fork, import or posted message never recomputes sampling for a trace that was never made.
|
Codex Review: Didn't find any major issues. 🎉 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
An object literal with none of the trace fields fails TypeScript's weak-type check against the helper's all-optional constraint; the spec now passes a typed row that declares the optional field, as every stored message type does.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e5bcb8830b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A non-success Langfuse response was turned into a TraceReadError without consuming or cancelling its body, and Node's fetch returns a connection to its pool only once the body is released, so a sustained upstream failure left sockets checked out until garbage collection. The body is now cancelled before the status becomes an error.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6cc751738
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Summary
LibreChat exports agent runs to Langfuse, but a user has no way to see what a response actually did (which model calls ran, how long each took to first token, which tool failed) without a Langfuse account and the right project. This adds a Trace view for the user's own conversations. It opens from a new header button next to Export/Share (or View trace in the mobile overflow menu) and covers the chat surface. It shows a summary row, a pinned waterfall overview with drag-to-focus intervals, a hierarchical ledger grouped by response, and a record inspector.
The feature is off by default (
interface.traceViewer.enabled). The control appears only on a persisted conversation the user owns that has at least one sampled trace in a readable destination. The client talks to a provider-neutral contract, and Langfuse is the first reader behind aTraceReaderseam.A response's trace sampling fields (
langfuseSampled,langfuseDestinationIds,langfuseRunId) decide which traces it owns, so client-authored writes (the message-create route and imports) now strip them through one helper,withoutTraceRefs. Forked conversations used to copy them onto messages with new ids. Those copies claimed traces they never produced, which would have shown a trace button over an empty trace and pointed feedback scores at nonexistent traces. Forks now drop them.A failed turn's error row (
<userMessageId>_) never recorded sampling, and its id is not the id of the run that failed, so failed runs were missing from the trace. The error row now stores the run's sampling fields pluslangfuseRunIdwhen the run was created (getFailedTurnTraceFieldsinpackages/api; the controller only passes whether the run exists). The trace viewer and feedback scores follow that run's trace, andlangfuseRunIdgets the same ingress stripping.How it works
sequenceDiagram participant Header participant Routes as /api/traces participant Reader as Langfuse reader participant Mongo participant Langfuse Header->>Routes: GET :conversationId/availability par ownership Routes->>Mongo: getConvoOwnership (owner, not a child thread) and existence Routes->>Reader: isAvailable Reader->>Mongo: hasSampledTraceMessage(destinationIds) end Routes-->>Header: available (gated on ownership, no Langfuse call) Header->>Routes: GET :conversationId/records[?cursor] Routes->>Mongo: getConvoOwnership Routes->>Reader: listRecords Reader->>Mongo: getConversationTraceRefs opt a turn more than one project could hold Reader->>Langfuse: GET v2/observations?filter=[sessionId, traceId any of, parentObservationId is null] (which traces this project holds) end Reader->>Langfuse: GET /api/public/v2/observations?filter=[sessionId, traceId any of (one segment's turns), startTime] Reader-->>Routes: normalized TTraceRecord[] (owned traces only) + sourceId + nextCursor (turn, project, Langfuse cursor)tenantId, and tenantless is its own scope. The reader then narrows the Langfuse session to traces derived from the user's own sampled response ids: the run tracesha256(messageId)and its title runsha256("title-" + messageId). Conversation ids are unique per user, not globally, so the session filter alone is not the boundary. Message ids can also be influenced by a request, so trace ids alone aren't a boundary either: every Langfuse read also requires the requester's internal user id as the traceuserId. The server stamps that id at export (verified on Langfuse Cloud: the owner's filter returns every observation, anyone else's none), so a colliding trace id never returns another user's observations. A deployment that exports another allowed user field vialangfuse.trace.userIdFieldhas no immutable principal on its traces (a field the exporter ignores still exports the internal id and keeps working), since names and usernames repeat and emails and provider ids repeat across tenants. For such a deployment the viewer reports no trace and logs why once. Only server-authored responses count (isCreatedByUser: false, notisUserSubmitted). The message-create route, conversation imports and forks replacelangfuseSampled/langfuseDestinationIds/langfuseRunIdwith an explicitlangfuseSampled: false, so a client can't forge a claim to someone else's trace, and a feedback score on a copied row never recomputes sampling for a trace that was never made. Credentials and Langfuse URLs never reach the browser.getScoreDestinations, which covers central env credentials, fanout tenant, and the admin connection. Destination resolution never waits on the central project lookup. Availability is a single existence query across every sampled response, and it runs alongside the ownership check. While central's project id is still resolving, it answers withretryAfterMsrather than a definitive no. A list read loads sampled responses a segment at a time: at most 50, ending at the turn the page starts from, plus one older response that marks where the next page begins. A long conversation's pages therefore never reload the responses before them. The cursor carries the anchor's position (creation time, then_id), so that read and the first-message lookup run together in one round trip. It pages by turn segments, newest first, and each segment is read from one project shown to hold it. Recorded destination ids only say where a trace was eligible to go, so a trace that more than one readable project could hold is looked up first. A turn's run and its title run are exported separately, so each trace id gets its own evidence. Each project is asked which of the window's traces it holds a root observation for (parentObservationId is null: one small row per trace), preferred project first and later ones only for traces still unaccounted for. A turn only one project could hold needs no lookup, so single-project deployments make no extra calls. Each trace is read from the preferred project that holds it. A trace no project shows joins an adjacent segment the project could hold it in: a title run the turn never had, or a run still in progress whose root isn't exported yet. When a turn's run and title run sit in different projects, the probes' root start times decide which is read first. Pages are ordered by turn: a turn's records may continue on the next page, and the client groups them by turn and orders them by time. A segment is a stretch of consecutive traces (up to 50 turns' worth) read from the same project. It asks Langfuse only for those turns' run and title traces (traceId any of, verified on Langfuse Cloud with 100 ids and cursor paging), so pages stay in turn order whichever projects held which turns. Paging within a segment uses that project's own Langfuse cursor, which handles records sharing a start time, and the page's record budget bounds that one read. A project that fails on a fresh read is dropped for the request, and its traces move to another project that holds them. A run that only a failed project could hold, or a title run a probe found in a failed project, raises that failure instead of an empty or partial trace, and it raises it at its own place in the page order. A cursor names the segment's first trace, the project, its Langfuse cursor and a hash of the segment's trace set. A continuation never moves to another project or replays pages: its failure is returned as is, and a cursor the segment no longer matches (or Langfuse rejects) returnsinvalid_request. The viewer answers that by reloading from the newest page. Sampled responses are ordered bycreatedAtthen_id, so every request rebuilds the same turn order a cursor was positioned in. Detail reads carry the turn the list attributed the record to (?message=), load only that sampled response, and ask Langfuse only within that turn's traces. They start with the listing page's project, then ask the remaining ones until the record turns up, reporting a failure rather than a 404 when a failed project could hold it. The route injects the destination resolver, the HTTP client and the rate-limit store (resolveLangfuseReadDestinations,fetch,limiterCache).core,basic,time,model,usagefor the list, paged by Langfuse cursors up tomaxRecordsper request, each round trip bounded byrequestTimeoutMs. Each request is bounded by the conversation's message times. The detail read is anid+sessionIdfilter, and it asks forio,metadataonly whenshowInputOutputis on, truncated tomaxContentLength. Upstream 401/403, 404 (no v2 API, e.g. self-hosted v3), 429, timeouts, malformed bodies and pages whose rows all fail the schema map to typed error codes the UI explains. Input and output that are literally the strings{}ornullare shown as written. Closing the viewer or dropping the connection aborts the Langfuse request (client query signal → server disconnect →AbortSignal.anywith the timeout). Costs are stripped server-side unlessinterface.contextCostis on, and the summary omits a cost total when any model call has no price. Open in Langfuse only appears when the linked project (the session link now reports itsdestinationId) served every loaded page.model.tsbuilds the tree from flat, possibly partial pages. Records whose parent is missing, in another turn, or part of a cycle become roots instead of disappearing. Bars scale to their own response until an interval is focused, and generations split TTFT from decoding. Running records show a start marker, not an invented duration. Rows are windowed, and the ledger follows the ARIA tree pattern viaaria-activedescendant, keeping the active row mounted while it is scrolled out of the window. Times and durations format in the app language and the user's 12/24-hour setting. A settled run invalidates cached pages, so reopening shows the new turn. Refresh (and a settled run) keeps only the newest page, so a focused interval or selection that pointed at older pages is fitted to what remains or cleared. The chat stays mounted andinertunderneath, so closing restores scroll, draft and focus.Change Type
Testing
Focused suites, all passing locally:
packages/api:src/langfuse/reader.spec.ts(60),src/langfuse/feedback.spec.ts(70),src/traces/handlers.spec.ts(22),src/admin/langfuse.handler.spec.ts,src/langfuse+src/app/permissions.spec.ts(205)packages/data-schemas:src/methods/message.traces.spec.ts(10, mongodb-memory-server, includes tenant isolation and same-millisecond ordering),src/methods/message.spec.ts(136),src/app/interface.spec.ts(20)packages/data-provider:specs/config-schemas.spec.ts -t traceViewer(3)api:server/routes/__tests__/traces.spec.js(real@librechat/apihandlers + reader, fetch stubbed at the boundary),server/utils/import/fork.spec.js,server/utils/import/importers.spec.js,server/routes/__tests__/messages-{get,feedback}.spec.js,server/controllers/agents/__tests__/request.resumeMetadata.spec.js(139)client:Chat/Trace/__tests__/{model,format,Viewer,Surface}(64),Menus/__tests__/HeaderMenu.spec.tsx,__tests__/ChatView*.spec.tsxRegression checks: I removed the trace-ownership filter, the fork fix, the parallel availability lookup, the non-blocking destination resolution, the disconnect guard, failure-behind-empty reporting, detail probing, the trim reset, segment boundaries, the segment size bound, run-id ownership, all-malformed pages, the user-id read filter, the configured-user-field refusal (and the ignored-field fallback), bounded response windows, the repeated probe and list cursors, single-turn detail authorization, detail refresh, the focus fallback, failed title reads, evidence over eligibility, per-trace evidence, split title ordering, unverifiable-turn reporting, continuations that neither fail over nor replay, the viewer's reload on a changed trace, same-millisecond ordering, the running-turn fallback, the unpriced-generation rule, literal content strings, the active-row mount and the error row's run fields one at a time, and confirmed their tests fail without them.
npx tsc --noEmitpasses inpackages/api,packages/data-schemasandclient, andpackages/data-providerbuilds with tsc.node scripts/static-checks.mts --full --against origin/devpasses, with the unused-npm-packages check skipped because depcheck is not installed.Manual run against real data. I ran the backend from this branch on a local mongod with the deployment's Langfuse Cloud credentials. I seeded a user who owns two conversations whose message ids match existing Langfuse traces: 59 responses / 421 observations, and one response plus its title run.
In Playwright:
Confirmed against the live API:
sha256(messageId)andsha256("title-" + messageId)model(not the SDK'sprovidedModelName); the reader accepts bothTest Configuration:
Plus Langfuse tracing configured (central
LANGFUSE_*credentials, fanout tenant, or the admin connection) on Langfuse Cloud or a self-hosted release that serves/api/public/v2/observations.Checklist