Skip to content

Add expandable command and file-change activity boxes - #3173

Closed
Quicksaver wants to merge 67 commits into
pingdotgg:mainfrom
Quicksaver:split/file-command-activity-boxes
Closed

Add expandable command and file-change activity boxes#3173
Quicksaver wants to merge 67 commits into
pingdotgg:mainfrom
Quicksaver:split/file-command-activity-boxes

Conversation

@Quicksaver

@Quicksaver Quicksaver commented Jun 19, 2026

Copy link
Copy Markdown

Summary

This adds compact, expandable activity rows for commands, file changes, and generic tool calls in the chat timeline. Command rows expose execution metadata and streamed output, while file-change rows expose changed paths and inline diffs without making the collapsed timeline noisy.

The implementation normalizes provider activity in a dedicated parser, composes it into public work-log entries, and keeps expansion, scrolling, and keyboard behavior predictable as activity streams arrive.

What Changed

  • Added a dedicated work-log activity parser that extracts stdout, stderr, fallback output, exit code, duration, patch text, and changed-file metadata from heterogeneous provider payloads.
  • Kept session logic focused on timeline ordering, lifecycle collapse, and public work-log composition while preserving provider-specific parsing in the activity module.
  • Merged incremental command output without duplicating repeated snapshots or discarding longer completed output, and merged cumulative patch snapshots without corrupting earlier detail.
  • Added bounded patch discovery and normalization for unified diffs, Codex-style file-change records, additions, deletions, and oversized-patch fallbacks.
  • Kept streamed activity ordering deterministic, including same-timestamp tool updates with and without stable match keys.
  • Added compact activity headings and previews derived from one shared display model, including failed status for non-zero command exit codes and exact 0ms durations.
  • Added expandable command detail panels with command/raw-command text, status metadata, stdout/stderr or fallback output, tail truncation, and full-output expansion.
  • Added expandable file-change panels with normalized path previews, changed-file chips, inline rendered diffs, raw-patch fallback, and explicit display of files not represented by a renderable diff.
  • Added safe generic and MCP detail expansion that preserves repeated argument objects, redacts circular references, and leaves collab-agent activity under its dedicated nested UI.
  • Preserved nested control behavior inside expandable rows, supported Enter/Space row toggling, and stopped live-follow on keyboard or native-scrollbar navigation without intercepting editable controls.
  • Added focused unit and rendering coverage for work-log extraction and merging, activity display derivation, diff/path matching, MCP serialization, expansion behavior, and keyboard interactions.

Why

Command and file-change events are otherwise mostly opaque in the timeline. Expandable details make it possible to debug command output, inspect intermediate or git-ignored file changes, and follow an agent's work more closely while preserving a compact default view.

Validation

  • pnpm exec vp test run --passWithNoTests --project unit src/lib/workLogActivity.test.ts src/session-logic.test.ts src/components/chat/MessagesTimeline.logic.test.ts src/components/chat/MessagesTimeline.test.tsx passed: 4 files, 169 tests.
  • git diff --check upstream/main...HEAD passed.

Proof

Before:
Collapsed activity rows before the change

After:
Compact expandable activity rows after the change

Expanded command output with tail truncation:
Expanded command output with tail truncation

Expanded inline file-change diffs:
Expanded inline file-change diffs


Note

Medium Risk
Large UI and session-timeline change with complex provider payload merging; mistakes could mislabel tool types or corrupt streamed command output, but behavior is heavily unit-tested and scoped to display/work-log composition.

Overview
Chat work-log rows for commands and file changes are now clickable and expandable instead of a single generic <pre> dump. Collapsed rows keep compact previews (Ran command - …, Changed files - …); expansion shows structured panels.

Work-log pipeline: session-logic gains fields for stdout/stderr, exit code, duration, patches, and changed files, populated via new workLogActivity parsing and cumulative merge rules across tool.updated / tool.completed (incremental output, patch prefixes, Codex-style diffs, bounded patch size). Non-zero exit codes count as tool failure; 0ms durations render explicitly.

UI: MessagesTimeline renders command blocks (raw command, metadata, tail-truncated stdout/stderr with expand) and file-change blocks (FileDiff inline, path chips, files without renderable diffs). Generic/MCP details use safe JSON serialization; file-read rows with only paths stay generic. Row Enter/Space toggles only when the row itself is focused.

Tests cover extraction, merging, display derivation, path matching, and static markup for expandable rows.

Reviewed by Cursor Bugbot for commit 74152eb. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add expandable command and file-change activity boxes to work log entries

  • Work log entry rows in the messages timeline are now expandable sections showing structured command details (command, stdout/stderr, exit code, duration) or file-change details (changed file chips, inline diffs with path/stat headers, or raw patch).
  • WorkLogEntry gains new optional fields (output, stdout, stderr, exitCode, durationMs, patch) populated via a new unified payload parser in workLogActivity.ts.
  • Cumulative merging of output streams and patches across tool lifecycle events is handled by new aggregators (mergeCumulativeOutput, mergeCumulativePatch) so streaming updates display coherently.
  • New display utilities in workLogEntryDetails.ts and MessagesTimeline.logic.ts derive headings, previews, and truncated output views; SimpleWorkEntryRow in MessagesTimeline.tsx wires these together with ARIA expand/collapse attributes.
  • Keyboard toggling is restricted to row-level Enter/Space events via shouldToggleWorkEntryRowFromKeyDown, and non-zero exitCode now marks a tool entry as a failure.
  • Behavioral Change: formatDuration now returns "0ms" for zero-millisecond durations instead of rounding up to "1ms".

Macroscope summarized 74152eb.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 35477d70-81bb-4818-a692-001be9929d48

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Jun 19, 2026
Comment thread apps/web/src/session-logic.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

2 blocking correctness issues found. This PR introduces substantial new UI capability (expandable command/file-change rows) with ~1400 lines of new parsing and display logic across two new files. Multiple unresolved review comments identify potential correctness bugs in the output merging and quote handling logic.

You can customize Macroscope's approvability policy. Learn more.

- Treat whitespace-only stdout as absent\n- Fall back to aggregated command output
- Keep blank stream chunks for live command updates
- Avoid synthetic separators around whitespace chunks
- Preserve incremental chunks without injecting separators
- Keep blank-only raw output content during tool updates
- Avoid trim allocation when checking command output
Comment thread apps/web/src/session-logic.ts Outdated
Comment thread apps/web/src/session-logic.ts Outdated
Comment thread apps/web/src/session-logic.ts Outdated
Comment thread apps/web/src/session-logic.ts Outdated
- Move expandable work entry logic into a shared module
- Re-export the moved helpers from MessagesTimeline.logic
- Keep the timeline component focused on rendering
- Remove duplicate timeline helper re-exports
- Stream command-output tail and dedupe calculations
- Safely serialize MCP tool data and cover edge cases
- Match absolute changed files against workspace-relative diffs

- Cover basename root diffs without hiding nested namesakes
Comment thread apps/web/src/lib/workLogEntryDetails.ts
Comment thread apps/web/src/lib/workLogEntryDetails.ts Outdated
- Keep changed-file-only reads in generic detail panels

- Preserve repeated MCP argument objects while redacting cycles
- Preserve expandable file and command activity details
- Bring in mobile, relay, legal, and web performance updates
Comment thread apps/web/src/session-logic.ts Outdated
Comment thread apps/web/src/session-logic.ts Outdated
Comment thread apps/web/src/session-logic.ts Outdated
Comment thread apps/web/src/lib/workLogEntryDetails.ts
- Keep cumulative and oversized patch payloads renderable
- Reflect non-zero exits and exact zero durations in row details
- Add focused regressions for all four review findings
- Document file and command activity behavior
- Record focused tests and development ports
- Clarify current behavior and conflict-resolution guidance
- Document ownership of activity parsing, timeline ordering, and rendering
Comment on lines +197 to +206
function trimMatchingOuterQuotes(value: string): string {
const trimmed = value.trim();
if (
(trimmed.startsWith("'") && trimmed.endsWith("'")) ||
(trimmed.startsWith('"') && trimmed.endsWith('"'))
) {
const unquoted = trimmed.slice(1, -1).trim();
return unquoted.length > 0 ? unquoted : trimmed;
}
return trimmed;

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.

🟡 Medium lib/workLogActivity.ts:197

trimMatchingOuterQuotes strips a pair of quotes whenever the string begins and ends with the same quote character, even when those quotes belong to separate argument tokens. For a wrapper remainder like "Write-Output" "hello world", it removes the opening quote of the first argument and the closing quote of the last, producing Write-Output" "hello world — a malformed command that corrupts the displayed output. Consider only stripping quotes when they form a single enclosing pair (e.g., by verifying no other same-quote character appears between them).

 function trimMatchingOuterQuotes(value: string): string {
   const trimmed = value.trim();
   if (
-    (trimmed.startsWith("'") && trimmed.endsWith("'")) ||
-    (trimmed.startsWith('"') && trimmed.endsWith('"'))
+    (trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.indexOf("'", 1) === trimmed.length - 1) ||
+    (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.indexOf('"', 1) === trimmed.length - 1)
   ) {
     const unquoted = trimmed.slice(1, -1).trim();
     return unquoted.length > 0 ? unquoted : trimmed;
   }
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/lib/workLogActivity.ts around lines 197-206:

`trimMatchingOuterQuotes` strips a pair of quotes whenever the string begins and ends with the same quote character, even when those quotes belong to separate argument tokens. For a wrapper remainder like `"Write-Output" "hello world"`, it removes the opening quote of the first argument and the closing quote of the last, producing `Write-Output" "hello world` — a malformed command that corrupts the displayed output. Consider only stripping quotes when they form a single enclosing pair (e.g., by verifying no other same-quote character appears between them).

Comment on lines +130 to +138
if (previous.startsWith(next)) {
if (shouldKeepLongerOutputSnapshot(previous, next, nextActivityKind)) {
return previous;
}
if (nextActivityKind === "tool.updated" && (next.length === 1 || previous.includes("\n"))) {
return `${previous}${next}`;
}
return next;
}

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.

🟡 Medium lib/workLogActivity.ts:130

mergeCumulativeOutput drops accumulated output when a multi-character incremental chunk is also a prefix of the previous output. For example, mergeCumulativeOutput("hello", "he", "tool.updated") returns "he" instead of "hellohe", erasing already-displayed output. The previous.startsWith(next) branch treats any next that is a prefix of previous as a shorter snapshot, but for tool.updated an incremental chunk should be appended unless it is a single character or previous already contains a newline. Consider guarding the single-character/line-based conditions before falling back to next so multi-character incremental prefixes are concatenated.

  if (previous.startsWith(next)) {
-    if (shouldKeepLongerOutputSnapshot(previous, next, nextActivityKind)) {
-      return previous;
-    }
-    if (nextActivityKind === "tool.updated" && (next.length === 1 || previous.includes("\n"))) {
-      return `${previous}${next}`;
-    }
-    return next;
+    if (nextActivityKind === "tool.updated" && (next.length === 1 || previous.includes("\n"))) {
+      return `${previous}${next}`;
+    }
+    if (shouldKeepLongerOutputSnapshot(previous, next, nextActivityKind)) {
+      return previous;
+    }
+    return next;
  }
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/lib/workLogActivity.ts around lines 130-138:

`mergeCumulativeOutput` drops accumulated output when a multi-character incremental chunk is also a prefix of the previous output. For example, `mergeCumulativeOutput("hello", "he", "tool.updated")` returns `"he"` instead of `"hellohe"`, erasing already-displayed output. The `previous.startsWith(next)` branch treats any `next` that is a prefix of `previous` as a shorter snapshot, but for `tool.updated` an incremental chunk should be appended unless it is a single character or `previous` already contains a newline. Consider guarding the single-character/line-based conditions before falling back to `next` so multi-character incremental prefixes are concatenated.

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 74152eb. Configure here.

const stdout = hasRenderableCommandOutput(workEntry.stdout) ? workEntry.stdout : null;
const stderr = hasRenderableCommandOutput(workEntry.stderr) ? workEntry.stderr : null;
const output =
!stdout && !stderr && hasRenderableCommandOutput(workEntry.output) ? workEntry.output : null;

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.

Partial stdout hides completed output

Medium Severity

The command output display logic prioritizes stdout and stderr over the output field. This can result in incomplete or missing command output in expanded details when stdout contains an earlier, partial, or blank streamed version, while output holds the complete final result.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 74152eb. Configure here.

}}
>
{outputDisplay.visibleValue}
</button>

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.

Output block blocks text copy

Medium Severity

CommandOutputBlock renders stdout/stderr inside a button, and disables that button whenever output is not truncated. Short command streams therefore become hard or impossible to select and copy, which undercuts the main reason to expand command rows for debugging. Longer streams stay clickable for truncation toggles, so drag-to-select can also flip expansion unexpectedly.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 74152eb. Configure here.

@juliusmarminge

Copy link
Copy Markdown
Member

this will conflict a lot w/ orchestrator #2829 so we can revisit it later

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants