qwen-code/packages/web-shell/client/components/messages/tools/ToolChrome.module.css
Shaojin Wen 800507598c
feat(web-shell): reveal full tool detail and auto-collapse finished tools (#5088)
* feat(web-shell): reveal full tool detail and auto-collapse finished tools

Long tool descriptions were hard-capped at 120 characters and finished
tools (shell/edit/write) stayed expanded indefinitely, so commands were
unreadable and the transcript filled with stale output.

- Lift the 120-char description cap so the full command/path reaches the
  DOM; collapsed rows ellipsise via CSS (adapts to width) and a click
  reflows the full text into a wrapped block below the header.
- Add a leading disclosure chevron; any row with detail output or a long
  description is now expandable.
- Auto-collapse a tool to its one-line summary once it completes
  successfully. Running tools stay expanded (live output) and failures
  stay expanded (error visible); agents keep their own manual expand state.

* fix(web-shell): preserve manual expand on completion; correct auto-expand comment

Review feedback on #5088:

- shouldAutoExpand: rewrite the comment to match the code. Only the verbose
  kinds (shell/edit/write/ask) auto-expand and stay expanded on failure; other
  kinds are collapsed by default (their summary line shows the outcome and they
  stay click-to-expand). Force-expanding every failed tool was rejected because
  tools without an expanded-detail renderer would then hide the summary line
  and show an empty body — i.e. hide the error.
- Auto-collapse-on-completion no longer overrides an explicit user toggle: a
  userToggledRef latch (set on header click, reset on tool-identity change)
  guards the collapse effect, so a row the user expanded/collapsed keeps its
  state when the tool finishes.

* test(web-shell): assert tool-detail relocation via DOM, not textContent

Review feedback (#5088): the expand test asserted container.textContent
contains the command before and after the click, which passes regardless of
whether the description is relocated from the header span to the wrapped
block (textContent concatenates the whole subtree). Assert the DOM move
instead — the command is in a leaf <span> while collapsed and in none while
expanded — so a regression dropping the relocation now fails the test.
2026-06-14 10:11:13 +08:00

245 lines
3.8 KiB
CSS

.group {
margin-bottom: 12px;
margin-left: 14px;
padding: 8px 14px;
border: 1px solid var(--border-color);
border-radius: var(--radius);
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
max-width: 100%;
overflow: hidden;
}
.line {
padding: 2px 0;
min-width: 0;
}
.lineMain {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
/* Leading disclosure column. Rendered (blank) for every tool row so names stay
aligned; shows ▸/▾ only when the row can expand. */
.lineToggle {
width: 12px;
flex-shrink: 0;
font-size: 10px;
line-height: 1;
color: var(--text-dimmed);
text-align: center;
user-select: none;
}
.icon {
font-size: 13px;
flex-shrink: 0;
}
.iconDone {
color: var(--success-color);
}
.iconError {
color: var(--error-color);
}
.iconPending {
color: var(--text-dimmed);
}
.iconSpin {
color: var(--warning-color);
display: inline-block;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.lineName {
font-weight: 700;
color: var(--text-primary);
font-size: 13px;
flex-shrink: 0;
}
.lineArg {
color: var(--text-secondary);
font-size: 13px;
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.lineElapsed {
margin-left: auto;
color: var(--text-dimmed);
font-size: 12px;
flex-shrink: 0;
}
.lineExpandable {
cursor: pointer;
}
.lineExpandable:hover .lineName {
color: var(--accent-color);
}
.lineChevron {
font-size: 10px;
color: var(--text-dimmed);
flex-shrink: 0;
margin-left: 6px;
}
.agentSummary {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
margin-left: 22px;
margin-top: 2px;
}
.lineOutput {
margin-left: 16px;
font-size: 12px;
color: var(--text-dimmed);
white-space: pre-wrap;
overflow-wrap: anywhere;
line-height: 1.4;
}
/* Full, wrapped tool argument shown below the header when the row is expanded
(the header drops its single-line ellipsised copy). Aligns with the expanded
output block below it. */
.lineFullArg {
margin-left: 16px;
margin-top: 2px;
font-size: 12px;
line-height: 1.4;
color: var(--text-secondary);
font-family: var(--font-mono);
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: anywhere;
}
.lineDetail {
}
.expandedBash,
.expandedRead {
overflow: hidden;
}
.expandedOutput {
padding: 0px 0px 0 16px;
font-size: 12px;
line-height: 1.5;
color: var(--text-secondary);
white-space: pre-wrap;
word-break: break-word;
overflow-x: auto;
max-height: 400px;
overflow-y: auto;
margin-bottom: 0;
}
.expandedEdit {
margin-top: 2px;
}
.expandedWrite {
border: 1px solid var(--border-color);
border-radius: 4px;
overflow: hidden;
}
.writeAdd {
color: var(--success-color);
}
.expandBtn {
display: block;
width: 100%;
padding: 3px 10px;
margin-top: 10px;
font-size: 11px;
font-family: var(--font-mono);
background: var(--subtle-bg);
border: none;
color: var(--text-dimmed);
cursor: pointer;
text-align: left;
}
.expandBtn:hover {
color: var(--accent-color);
background: var(--info-bg);
}
.todoList {
padding: 6px 0 4px 24px;
display: flex;
flex-direction: column;
gap: 2px;
}
.todoItem {
font-size: 13px;
font-family: var(--font-mono);
line-height: 1.6;
padding-left: 4px;
}
.todoDone {
color: var(--text-primary);
}
.todoActive {
font-weight: 600;
}
.todoPending {
color: var(--text-dimmed);
}
.compactGroup {
margin-bottom: 12px;
margin-left: 14px;
padding: 8px 14px;
border: 1px solid var(--border-color);
border-radius: var(--radius);
}
.compactHeader {
display: flex;
align-items: baseline;
gap: 8px;
min-width: 0;
}
.compactCount {
color: var(--text-dimmed);
font-size: 13px;
flex-shrink: 0;
}
.compactHint {
font-size: 12px;
color: var(--text-dimmed);
margin-top: 4px;
}