Skip to content

🪜 feat: Title an Open Activity Fold and Reach Failed Calls in One Click - #16390

Merged
danny-avila merged 5 commits into
devfrom
danny-avila/librechat-visualizer-ux-28f069
Sep 27, 2026
Merged

danny-avila merged 5 commits into
devfrom
danny-avila/librechat-visualizer-ux-28f069

Conversation

@danny-avila

@danny-avila danny-avila commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

An expanded activity phase or tool group is hard to read as a group. Its header keeps the same weight and colour open or closed, while the run is live the header shows the newest row's text instead of the group's name, and a phase header, a sub-group header and a tool row all share one left edge and one type size, so the reader cannot tell what sits under what. A failed call is a · failed suffix on one row and a · 1 failed note on the header: reaching the error takes opening the fold, scanning for the suffix, and opening that row.

This PR keeps the phase → tool group → row → output hierarchy and changes how an open fold and a failure present. An open header steps up to primary semibold, is titled by its stable label instead of the live line, and sticks to the top of the viewport while its rows scroll under it. The rows under an open header step in 24px beneath a hairline rail hung from the header's glyph, one level per fold. A failure is one click away from anywhere: a N failed pill beside the header opens the fold and every failed row's output panel, a collapsed card peeks its first failed call and error line on a row under the header, and the failed row itself carries a red stripe at its left edge, the error's first line as its subtitle, and names its subject by the model's intent, the way the live header and the peek already do.

Driving the change through the real app on the mock lane surfaced three defects that the component harness could not: the client recognised only two of the server's three failed-output shapes, so a schema-validation failure the server labelled partial rendered as a completed card with no pill; a row's label lost its last letters to an ellipsis whenever its subtitle overflowed, because the weighted shrink left it a fraction of a pixel short of its text; and the peek named an MCP call by its server while the row named the tool. All three are fixed here.

Related to the Discord thread with Berry: make the tool group name more visible when expanding, a quick way to expand failed calls, and be clearer which tools sit under the main group.

Failed question calls now give their shared disclosure the failed phase. Searches and image calls with failed output or run status use the regular tool-error disclosure, so their error is present, expandable and reachable from the pill rather than hidden or marked successful. Failed subagents open their existing details panel when available. Cancelled calls remain cancelled; older search errors that were not counted as failures stay hidden rather than newly reading as successful. Long failed-peek labels yield space to the fixed count and Show error action.

How it works

The open-fold treatment is classes only: FOLD_RAIL_CLASSES in rows.ts goes on each panel's clipping wrapper (the inset has to sit inside the overflow-hidden element, or the failed row's stripe drawn 12px left of the row is clipped away), and the phase header's wrapper takes sticky top-0 while open.

The reveal is a counter that travels down the fold tree, since expansion otherwise only flows up through onToolExpand:

ActivityPhaseGroup            pill / peek → handleRevealFailed: open, then reveal()
  useFailedRevealTrigger(ready = isExpanded && shouldRenderBody)   # increments only once rows are mounted
  FailedRevealContext.Provider
    ToolCallGroup             useFailedReveal(failedCount > 0, open + reveal())   # relays to its own rows
      FailedRevealContext.Provider
        ToolCall              useFailedReveal(phase === 'failed' && hasInfo, open panel + focus row)

Deferring the increment until ready is what makes one click on a closed card work: a collapsed card mounts its rows in the same commit that opens it, and a row that mounts under an already-advanced counter would take it as the resting value. Each group re-issues the request to its own rows for the same reason, so it reaches a row three lazy disclosures down.

The main reveal consumer lives in ProgressText, the disclosure for generic, bash, code, memory, file, skill and question cards. A failed search or image call routes through the generic ToolCall disclosure; a failed subagent claims the request and opens its own activity panel. An openable row uses its own toggle and puts focus on its labeled button. The request carries a one-shot focus claim, relayed through nested providers, so a phase with several failures opens all of them and focuses only the first in document order, the one its peek previewed.

Visible text stays deduplicated: while live, the header's failure count is spoken as part of the header's name (sr-only) and shown by the pill; a standalone group's text detail drops the failed note when its pill is showing. A stop count has no pill and stays in text.

The client's isError now matches the server's completedToolExecutionStatus: the tool-call error prefix, Error processing tool, and a bare Error: block closed by "Please fix your mistakes." (the SDK's schema-validation feedback, which leaves the run step completed). In ProgressText the label takes shrink-0 max-w-full whenever a subtitle is present and the subtitle takes all of the shrink; before, any overflowing subtitle cost the label its last letters.

Type of change

  • Feature
  • Bug fix
  • Tests / tooling / CI

Testing

Ran the real app on the mock e2e lane with a new fake-model scenario, E2E_ACTIVITY_FAILED_REPLY, whose middle batch calls slow_echo without its required text (fails schema validation) beside a 4-second slow_echo. Watched the fold stream (live header ticks "Running e2e-memory ×2"), then on the settled card: pill and peek present, peek click opens the phase, the group holding the failure and that row's output with focus on the row, header click closes it and brings the peek back, pill click reveals again, reload keeps pill and peek and the reveal still works, dark theme rendered. No console errors or page errors across the run. A second scenario without failures shows neither pill nor peek and opens its nested groups as before.

Also mounted the real ActivityPhaseGroup → ToolCallGroup → ToolCall under the real style.css in Chromium (vite harness, both themes) and confirmed the rail is a 1px border-medium line at a 24px inset on both levels, the failed row's stripe renders beside the rail, and scrolling leaves the open header pinned at the top.

Tested environments/configuration:

  • Browser: Chromium via Playwright, light and dark
  • Mock e2e lane (playwright.config.mock.ts, in-memory Mongo, fake model, fake MCP server), not a live provider

Automated tests:

  • Completion pass: 208 passed across 8 focused frontend suites on d734f7d3f (Part, WebSearch, SubagentCall, AskUserQuestionCall, ActivityPhaseGroup, ProgressText, ToolCallGroup, ToolCall). On final head 87e7b1f2a, 21 WebSearch tests passed, including the legacy-output compatibility case. npm run build:data-provider and client tsc --noEmit passed, including the final head.

  • npx playwright test --config=e2e/playwright.config.mock.ts activity-fold activity-phases — 3 passed (new e2e/specs/mock/activity-fold.spec.ts)

  • cd client && npx jest src/components/Chat/Messages/Content — 77 suites, 1210 tests

  • ProgressText.test.tsx: reveal opens through the row's own toggle and focuses its button, opens without focus when another row holds the claim, never toggles an open panel, ignores rows that did not fail or cannot open; fixed siblings stay out of the shrinking label box. ToolCallGroup.test.tsx: a standalone group shows its count once, on the pill; nested groups keep it in text. failed.test.ts: benign output on a failed step yields no error line.

  • Added __tests__/failed.test.ts (failed-line helper), cases in ActivityPhaseGroup.test.tsx, ToolCallGroup.test.tsx, ToolCall.test.tsx, ProgressText.test.tsx, OutputRenderer.test.tsx; three selectors in LiveParity.test.tsx updated for the pill and the header wrapper

  • npx tsc --noEmit in client (no errors in touched files; the worktree has pre-existing sandpack module-resolution errors unrelated to this change)

Before / after screenshots

Revision-pinned captures from the real mock app in desktop dark mode, using synthetic local fixtures.

Collapsed failed phase

Before and after the collapsed failed phase

Open phase hierarchy

Before and after the open activity fold

84e3ea9ed99d (before) → cb023ec251c3 (after).

An open phase or tool group now reads as the title of the rows under it:
the header steps up to primary semibold, pins the stable label instead of
the live line, and sticks to the top while its rows scroll. Rows step in
24px under a hairline rail hung from the header glyph, one level per fold.

A failure is one click away from anywhere. A "N failed" pill beside the
header opens the fold and every failed row's output; a collapsed card peeks
its first failed call and error line under the header; the failed row
carries a red stripe, the error's first line as its subtitle, and names its
subject by the model's intent as the live header and peek already do.

The reveal travels down a FailedRevealContext counter that a group relays to
its own rows only once they have mounted, so a request made on a closed
card reaches a row three lazy disclosures below it.
…Showed

A mock-lane spec runs a phase whose middle batch holds a call that failed
schema validation beside a slow one, watches the fold stream, then reaches
the error from the peek, from the pill, and again after a reload, in both
themes, with no console or page errors.

Three things the real app showed that the harness had not:

- The client counted only two of the server's three failed-output shapes,
  so a schema-validation failure that the server labelled `partial` rendered
  as a completed card with no pill. `isError` now mirrors
  `completedToolExecutionStatus`.
- A row's label lost its last letters to an ellipsis whenever its subtitle
  overflowed: the weighted shrink left the label a fraction of a pixel short.
  The subtitle now takes all of the shrink and the label caps at the row.
- The peek named an MCP call by its server while the row named the tool.
  Both now name it the way the row does, and the error line drops its bare
  `Error:` prefix the red glyph already speaks for.
@danny-avila
danny-avila force-pushed the danny-avila/librechat-visualizer-ux-28f069 branch from 1739475 to cb023ec Compare September 26, 2026 12:10
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-27T00:22:22.945754Z adfa8d2 Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb023ec251

ℹ️ 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".

Comment thread client/src/components/Chat/Messages/Content/ToolCall.tsx Outdated
Comment thread client/src/components/Chat/Messages/Content/ProgressText.tsx Outdated
Comment thread client/src/components/Chat/Messages/Content/ToolCall.tsx Outdated
Comment thread client/src/components/Chat/Messages/Content/reveal.tsx Outdated
Comment thread client/src/components/Chat/Messages/Content/live.ts
Comment thread client/src/components/Chat/Messages/Content/ToolCallGroup.tsx
The reveal was consumed by the generic ToolCall row alone, so a failed
bash, code, memory, file or skill card, each rendered through its own
component, opened its group and stopped. The consumer now lives in
ProgressText, the disclosure control every card renders: a failed, openable,
closed row opens through the card's own toggle, and the one row the request
lets take focus lands the reader on its labeled button rather than on an
unnamed wrapper. The request carries a one-shot focus claim, relayed
through nested providers, so a phase with several failures opens all of
them and focuses only the first in document order, the one its peek named.

Also from the same review: an error line is derived only from output that
is an error, so a step failed by its run status or a status attachment no
longer shows ordinary output in red; a standalone group shows its failure
count once, on the pill, keeping the text for the accessible name; and the
label and subtitle share one shrinking box so the verdict, duration and
chevron after them never lose width.
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review adfa8d2

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: adfa8d2e8a

ℹ️ 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".

Comment thread client/src/components/Chat/Messages/Content/ProgressText.tsx
Comment thread client/src/components/Chat/Messages/Content/ActivityPhaseGroup.tsx Outdated
@lia-by-librechat

Copy link
Copy Markdown
Contributor

Completion pass for head d734f7d3f8fc266e3941eacbc75d8add0bbd3f10: failed question cards now answer the fold reveal; validation- and status-failed web searches and image calls expose their error through the shared tool disclosure; failed subagents open their existing details panel; long failed-peek labels no longer displace the count or Show error action. The focused suites pass (208 tests in 8 suites) and the client typecheck passes. Please review this exact head; feedback on earlier commits does not cover these changes.

@lia-by-librechat

Copy link
Copy Markdown
Contributor

Current remote head 87e7b1f2a51212a9dece9329e408572ce8d99ef8 supersedes d734f7d. The follow-up preserves the previous hidden treatment for old, unclassified web-search 'error processing' text while recognized validation and status failures still show the expandable error card. WebSearch.test.tsx passes (21 tests), client tsc --noEmit passes, and touched-file ESLint/Prettier pass on this head; the earlier eight focused suites and CI were green before this compatibility addition. Please review this exact head.

@danny-avila
danny-avila merged commit a047548 into dev Sep 27, 2026
29 checks passed
@danny-avila
danny-avila deleted the danny-avila/librechat-visualizer-ux-28f069 branch September 27, 2026 01:25
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.

2 participants