Skip to content
This repository was archived by the owner on Jun 29, 2026. It is now read-only.

feat(chat): add inline activity rows for tool results#40

Merged
capy-ai[bot] merged 8 commits into
mainfrom
capy/inline-activity-tool-ui
Jun 26, 2026
Merged

feat(chat): add inline activity rows for tool results#40
capy-ai[bot] merged 8 commits into
mainfrom
capy/inline-activity-tool-ui

Conversation

@urjitc

@urjitc urjitc commented Jun 26, 2026

Copy link
Copy Markdown
Member

This PR replaces the current JSON-heavy tool card UI with subtle inline activity rows above assistant replies, making the chat cleaner for non-technical users.

Changes:

  • ai-chat-display-state.ts: Add AiChatToolActivity model and getToolActivity() function that derives one visible top-level tool row per message, with optional children for nested tools. Tool parts are no longer treated as displayable content in normal chat. Add deterministic summarizers for all workspace (create/delete/move/rename/edit/list/read), web (search/markdown/links), research (discover/deepen), and codemode tools with counts and item names. Hide sandbox_bash and time_* tools by default.

  • AiChatToolActivityRow.tsx: New component rendering a single inline activity row with spinner or icon, title, and deterministic summary (e.g., Updating workspace — Created 3 items). Supports disclosure of inner tool calls (children) and input/output details without exposing raw JSON by default. Uses gray/muted styling for failures to avoid alarming users.

  • AiChatAssistantPending.tsx: Export ThinkExThinkingMark with optional className prop for reuse in activity rows during running state.

  • AiChatMessageRow.tsx: Render AiChatToolActivityRow above assistant message prose when a visible activity is present, keeping tool UI separate from text content.

Behavior:

  • One visible activity row max per assistant turn, updating in place
  • Codemode shows as one top-level row; inner tool calls appear only on expand
  • Direct workspace reads and mutations use the same inline pattern
  • Failures surface softly without aggressive red chrome

Open ENG-119 ENG-119


View with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is enabled.


Summary by cubic

Replace JSON tool cards with inline activity rows at each tool call, showing deterministic summaries with higher-contrast shimmered progress; tool text matches assistant body size and the prompt enforces short present‑progressive tool titles. Expansion is restricted to codemode child summaries, meeting ENG-119 by keeping tool activity readable and unobtrusive.

  • New Features

    • Added AiChatToolActivityRow with higher-contrast shimmered progress, concise title, and deterministic summary; expands only for codemode to show nested tool summaries.
    • Introduced AiChatToolActivity with getToolActivityForPart() and summarizers for workspace (create/delete/move/rename/edit/list/read), web (search/markdown/links), research (discover/deepen), and codemode; hides sandbox_bash and time_*. Prompt now requires short, present‑progressive titles (e.g., “Reading workspace”).
    • Rendered activity inline via AiChatMessagePartView, grouping codemode_execute as a single row with aggregated children; exported ThinkExThinkingMark with optional className.
  • Bug Fixes

    • Hide assistant rows that contain only hidden tool parts to prevent empty assistant content.

Written for commit 8d16db2. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • AI chat now shows clearer tool activity summaries, including expandable grouped steps for longer actions.
    • Tool calls now display short, plain-English titles that read like ongoing activities.
  • Bug Fixes

    • Improved AI message rendering so tool-related content is grouped more consistently and displayed in the right order.
    • Assistant rows with tool UI content are now handled more reliably, reducing clutter in chat output.

…ic summaries instead of raw raw

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@urjitc urjitc added the capy Generated by capy.ai label Jun 26, 2026 — with Capy AI
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8e2568dc-f9ed-4b7d-a476-dd10a4b2b7a6

📥 Commits

Reviewing files that changed from the base of the PR and between c849f5e and 8d16db2.

📒 Files selected for processing (6)
  • src/features/workspaces/ai/ai-thread-runtime.ts
  • src/features/workspaces/components/ai-chat/AiChatAssistantPending.tsx
  • src/features/workspaces/components/ai-chat/AiChatMessagePartView.tsx
  • src/features/workspaces/components/ai-chat/AiChatMessageRow.tsx
  • src/features/workspaces/components/ai-chat/AiChatToolActivityRow.tsx
  • src/features/workspaces/components/ai-chat/ai-chat-display-state.ts

📝 Walkthrough

Walkthrough

Assistant chat rendering now filters tool UI from assistant rows, groups codemode executions with child tool activities, and renders tool activity summaries in chat parts. The workspace prompt now requires plain-English tool-call titles, and the thinking-mark component accepts a configurable class name.

Changes

AI chat tool activity flow

Layer / File(s) Summary
Renderable parts and summaries
src/features/workspaces/components/ai-chat/ai-chat-display-state.ts
AssistantRowDisplay now carries AiChatRenderablePart[], assistant rows with tool UI are hidden, codemode_execute can be grouped with visible tool children, and tool visibility, status, title, and summary helpers are added.
Tool activity rendering
src/features/workspaces/components/ai-chat/AiChatMessageRow.tsx, src/features/workspaces/components/ai-chat/AiChatMessagePartView.tsx, src/features/workspaces/components/ai-chat/AiChatToolActivityRow.tsx, src/features/workspaces/components/ai-chat/AiChatAssistantPending.tsx
AiChatToolActivityRow renders summaries with optional collapsible child activities and a running shimmer; message rows and part views now route renderable parts and tool-group keys into it, and ThinkExThinkingMark accepts an optional className.
Tool-call titles
src/features/workspaces/ai/ai-thread-runtime.ts
getAIThreadSoulPrompt() now requires short plain-English tool-call titles and constrains them to progressive activity phrases.

Sequence Diagram(s)

sequenceDiagram
  participant AiChatMessageRow
  participant getAssistantRowDisplay
  participant AiChatMessagePartView
  participant AiChatToolActivityRow

  AiChatMessageRow->>getAssistantRowDisplay: get assistant row display
  getAssistantRowDisplay-->>AiChatMessageRow: hidden or content parts
  alt content parts
    AiChatMessageRow->>AiChatMessagePartView: render each AiChatRenderablePart
    AiChatMessagePartView->>AiChatToolActivityRow: render tool UI / data-tool-group parts
    AiChatToolActivityRow-->>AiChatMessagePartView: summary, shimmer, nested child summaries
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

Hop, hop! I read the tool-call light,
With titles bright and phrased just right.
Tool groups bloom beneath the chat,
The thinking mark says “I’m on that!” 🐇
A tidy burrow, swift and neat.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch capy/inline-activity-tool-ui

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.

urjitc and others added 7 commits June 26, 2026 04:03
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@capy-ai
capy-ai Bot marked this pull request as ready for review June 26, 2026 05:21
@capy-ai
capy-ai Bot merged commit a4ecc13 into main Jun 26, 2026
4 checks passed
@greptile-apps

greptile-apps Bot commented Jun 26, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces raw JSON tool cards with lightweight inline activity rows, adding deterministic human-readable summaries for all workspace, web, research, and codemode tools and hiding invisible tool calls (sandbox_bash, time_*) from the chat UI entirely.

  • ai-chat-display-state.ts introduces AiChatToolGroupPart, getDisplayableParts grouping, and a full suite of tool summarizers; codemode child tools are collapsed under a single expandable row.
  • AiChatToolActivityRow.tsx renders the new inline row with a Shimmer animation while running and muted text when complete, with optional disclosure for codemode children.
  • AiChatMessagePartView and AiChatMessageRow are updated to dispatch the new group-part type and key it correctly; ThinkExThinkingMark is exported with an optional className prop.

Confidence Score: 4/5

Safe to merge — the changes are purely presentational and all tool data access is guarded through defensive helpers that fall back to empty arrays/strings on unexpected output shapes.

The new activity row component has a non-unique React key pattern that will produce console warnings (and potential list-reconciliation issues) whenever codemode calls the same tool more than once with an identical result string. The codemodeIndex tautology in getDisplayableParts is dead code rather than a runtime defect. The summarizeWorkspaceEdit success path is inconsistent with its failure counterpart. None of these would break the feature in normal use, but the key collision is worth addressing before the component sees heavy streaming traffic.

AiChatToolActivityRow.tsx — non-unique child keys and missing chevron state feedback; ai-chat-display-state.ts — redundant codemodeIndex condition and missing input-path fallback in summarizeWorkspaceEdit.

Important Files Changed

Filename Overview
src/features/workspaces/components/ai-chat/ai-chat-display-state.ts Core logic for deriving displayable parts and tool activity summaries; well-structured but has a redundant condition (always-true codemodeIndex check) and the edit summarizer's success path is missing the input-path fallback present in the failure path.
src/features/workspaces/components/ai-chat/AiChatToolActivityRow.tsx New activity row component; contains a non-unique React key pattern for nested children and a dead inner null-guard; chevron icon has no open/closed state rotation.
src/features/workspaces/components/ai-chat/AiChatMessagePartView.tsx Cleanly updated to dispatch tool-group parts to the new AiChatToolActivityRow; type guard is correct and safe against real data-tool-group message parts.
src/features/workspaces/components/ai-chat/AiChatMessageRow.tsx Type narrowed to AiChatRenderablePart and key generation extended for the new group part type; straightforward and correct.
src/features/workspaces/components/ai-chat/AiChatAssistantPending.tsx ThinkExThinkingMark exported with optional className prop; nullish-coalescing fallback is correct.
src/features/workspaces/ai/ai-thread-runtime.ts Two prompt lines added to require present-progressive tool titles from the model; minimal and safe change.

Fix All in Cursor

Reviews (1): Last reviewed commit: "style(ai-chat): increase running shimmer..." | Re-trigger Greptile

Comment on lines +40 to +41
<div
key={`${child.toolName}:${child.summary}`}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Non-unique React keys for repeated tool calls

The key ${child.toolName}:${child.summary} will collide when codemode invokes the same tool more than once with the same outcome — for example, two web_search calls that each return five results both produce the key "web_search:Found 5 sources". React will warn and may fail to reconcile the list correctly if items are ever reordered or removed during a streaming update. Using the iteration index as a tiebreaker (e.g., ${index}-${child.toolName}:${child.summary}) makes keys stable and unique.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor

Comment on lines +31 to +51
return (
<Collapsible className="w-fit max-w-full">
<CollapsibleTrigger className="w-fit max-w-full text-left">
<ActivitySummary activity={activity} canExpand />
</CollapsibleTrigger>
<CollapsibleContent className="mt-2 space-y-2 pl-7">
{nestedChildren.length > 0 ? (
<div className="space-y-1">
{nestedChildren.map((child) => (
<div
key={`${child.toolName}:${child.summary}`}
className="text-muted-foreground/80 text-sm"
>
{child.summary}
</div>
))}
</div>
) : null}
</CollapsibleContent>
</Collapsible>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 The inner nestedChildren.length > 0 guard is dead code — hasDetails (already checked above) guarantees this block is only entered with a non-empty array. Collapsing it also fixes the non-unique key issue noted on line 40.

Suggested change
return (
<Collapsible className="w-fit max-w-full">
<CollapsibleTrigger className="w-fit max-w-full text-left">
<ActivitySummary activity={activity} canExpand />
</CollapsibleTrigger>
<CollapsibleContent className="mt-2 space-y-2 pl-7">
{nestedChildren.length > 0 ? (
<div className="space-y-1">
{nestedChildren.map((child) => (
<div
key={`${child.toolName}:${child.summary}`}
className="text-muted-foreground/80 text-sm"
>
{child.summary}
</div>
))}
</div>
) : null}
</CollapsibleContent>
</Collapsible>
);
return (
<Collapsible className="w-fit max-w-full">
<CollapsibleTrigger className="w-fit max-w-full text-left">
<ActivitySummary activity={activity} canExpand />
</CollapsibleTrigger>
<CollapsibleContent className="mt-2 space-y-2 pl-7">
<div className="space-y-1">
{nestedChildren.map((child, index) => (
<div
key={`${index}-${child.toolName}:${child.summary}`}
className="text-muted-foreground/80 text-sm"
>
{child.summary}
</div>
))}
</div>
</CollapsibleContent>
</Collapsible>
);

Fix in Cursor

<Shimmer as="span" className="text-sm text-muted-foreground" duration={1.4}>
{activity.summary}
</Shimmer>
{canExpand ? <ChevronDown className="size-3 shrink-0" aria-hidden="true" /> : null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Chevron does not reflect open/closed state

The ChevronDown icon always points downward regardless of whether the Collapsible is open or closed. Radix's CollapsibleTrigger exposes data-state="open" / data-state="closed" on the trigger element, but there's no CSS or class-based rotation wired up. Users get no visual feedback that the section can be collapsed after opening it — the affordance is asymmetric and relies on muscle memory after the first click.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor

Comment on lines +168 to +172
result.push({
type: "data-tool-group",
part,
children: index === codemodeIndex ? codemodeChildren : [],
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 index === codemodeIndex is always true in this branch

codemodeIndex is parts.indexOf(codemodePart), and the loop enters the part === codemodePart branch exactly once — at that same index. The ternary therefore always evaluates to codemodeChildren and can never produce []. The condition can be removed without changing behaviour, and leaving it suggests to future readers that there is a scenario where children should be empty here, which there isn't.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor

Comment on lines +415 to +418
function summarizeWorkspaceEdit(output: unknown) {
const record = asRecord(output);
return `Updated ${quoteName(getBaseName(getString(record.path)))}`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 summarizeWorkspaceEdit success case has no input-path fallback

summarizeFailure for workspace_edit_item falls back to getPathFromToolInput(part.input) when the output doesn't contain a path field. The success path in summarizeWorkspaceEdit lacks this fallback — if the output's path is absent the display silently degrades to Updated item even though the input path is available. Applying the same fallback keeps the two paths consistent.

Fix in Cursor

@urjitc
urjitc deleted the capy/inline-activity-tool-ui branch June 28, 2026 02:14
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

capy Generated by capy.ai

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant