Align Agent Mode chat with normal chat - #614
Conversation
📝 WalkthroughWalkthroughAgent Mode and UnifiedChat now share reusable chat layouts and composer styling. Agent timeline items are grouped into turns with active-thinking detection, while permission, system, tool, empty-state, and thinking presentations are updated and tested. ChangesChat and agent timeline rendering
Estimated code review effort: 4 (Complex) | ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
frontend/src/components/AgentMode.tsx (2)
2647-2667: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCustom status
<span>replaces the shadcnBadgecomponent.
PermissionRowpreviously usedBadge; this now renders a manually styled<span>for the permission status text. Per path instructions, existing shadcn/ui components (src/components/ui/) should be used instead of hand-rolled UI elements. IfBadgecouldn't supportaria-live/role="status"directly, consider wrappingBadgewith those attributes rather than dropping it entirely.As per path instructions, "Use existing shadcn/ui components from
src/components/ui/instead of creating custom UI components" forfrontend/src/components/**/*.tsx.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/AgentMode.tsx` around lines 2647 - 2667, Update the permission status element in PermissionRow to use the existing shadcn Badge component instead of the manually styled span. Preserve the current status text, styling intent, role="status", and aria-live="polite" behavior by applying supported attributes to Badge or wrapping it when necessary.Source: Path instructions
2509-2621: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winConsider memoizing timeline grouping.
coalesceAdjacentThinkingItems,.filter(isRenderableTimelineItem),groupAgentTimelineItems, andactiveAgentThinkingItemIdall run unmemoized on every render ofAgentTimeline. SincetimelineItemsupdates on every streamed event, this recomputes and reallocates arrays each time. For long-running agent sessions this could add unnecessary work on the hot render path.♻️ Suggested refactor
function AgentTimeline({ items, isRunActive, onPermissionDecision }: { items: AgentTimelineItem[]; isRunActive: boolean; onPermissionDecision: (item: AgentTimelineItem, decision: AgentPermissionDecision) => void; }) { - const visibleItems = coalesceAdjacentThinkingItems(items).filter(isRenderableTimelineItem); - const turns = groupAgentTimelineItems(visibleItems); - const activeThinkingItemId = activeAgentThinkingItemId(visibleItems, isRunActive); + const visibleItems = useMemo( + () => coalesceAdjacentThinkingItems(items).filter(isRenderableTimelineItem), + [items] + ); + const turns = useMemo(() => groupAgentTimelineItems(visibleItems), [visibleItems]); + const activeThinkingItemId = useMemo( + () => activeAgentThinkingItemId(visibleItems, isRunActive), + [visibleItems, isRunActive] + );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/AgentMode.tsx` around lines 2509 - 2621, Memoize the derived timeline data in AgentTimeline: wrap coalesceAdjacentThinkingItems, the isRenderableTimelineItem filter, groupAgentTimelineItems, and activeAgentThinkingItemId in a useMemo keyed by items and isRunActive. Preserve the existing rendering behavior while avoiding recomputation when those inputs are unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/components/chat/ChatTurn.tsx`:
- Around line 118-119: Reformat the CHAT_COMPOSER_TEXTAREA_CLASS declaration and
the similarly affected shared class string around the referenced lines so every
source line is at most 100 characters. Split each literal across adjacent
strings while preserving the exact generated class names and styling.
---
Nitpick comments:
In `@frontend/src/components/AgentMode.tsx`:
- Around line 2647-2667: Update the permission status element in PermissionRow
to use the existing shadcn Badge component instead of the manually styled span.
Preserve the current status text, styling intent, role="status", and
aria-live="polite" behavior by applying supported attributes to Badge or
wrapping it when necessary.
- Around line 2509-2621: Memoize the derived timeline data in AgentTimeline:
wrap coalesceAdjacentThinkingItems, the isRenderableTimelineItem filter,
groupAgentTimelineItems, and activeAgentThinkingItemId in a useMemo keyed by
items and isRunActive. Preserve the existing rendering behavior while avoiding
recomputation when those inputs are unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 41e5ba8b-054a-46e3-83ee-ffb7b01a843e
📒 Files selected for processing (6)
frontend/src/components/AgentMode.tsxfrontend/src/components/UnifiedChat.tsxfrontend/src/components/chat/ChatTurn.tsxfrontend/src/components/markdown.tsxfrontend/src/services/agentTimeline.test.tsfrontend/src/services/agentTimeline.ts
| export const CHAT_COMPOSER_TEXTAREA_CLASS = | ||
| "w-full min-h-[52px] landscape-short:min-h-[40px] max-h-[200px] landscape-short:max-h-[100px] resize-none border-0 bg-transparent py-3.5 landscape-short:py-2 pl-4 pr-2 leading-6 focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:text-muted-foreground/60"; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Keep the shared class strings within the 100-character limit.
The declarations at Line 119 and Line 131 exceed the required limit. Split the literals across adjacent strings without changing the generated class names.
Suggested formatting
export const CHAT_COMPOSER_TEXTAREA_CLASS =
- "w-full min-h-[52px] landscape-short:min-h-[40px] max-h-[200px] landscape-short:max-h-[100px] resize-none border-0 bg-transparent py-3.5 landscape-short:py-2 pl-4 pr-2 leading-6 focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:text-muted-foreground/60";
+ "w-full min-h-[52px] landscape-short:min-h-[40px] max-h-[200px] "
+ "landscape-short:max-h-[100px] resize-none border-0 bg-transparent "
+ "py-3.5 landscape-short:py-2 pl-4 pr-2 leading-6 "
+ "focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:text-muted-foreground/60";As per coding guidelines, TypeScript/React code must enforce a 100-character line limit.
Also applies to: 129-132
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/chat/ChatTurn.tsx` around lines 118 - 119, Reformat
the CHAT_COMPOSER_TEXTAREA_CLASS declaration and the similarly affected shared
class string around the referenced lines so every source line is at most 100
characters. Split each literal across adjacent strings while preserving the
exact generated class names and styling.
Source: Coding guidelines
Summary
Thinkingfrom completedThoughtWhy
Agent Mode had developed a parallel orange-and-boxed chat presentation that no longer matched Maple's normal chat experience. Sharing the same rendering primitives makes normal chat the actual golden implementation and reduces future visual drift while keeping Agent-only tools and approvals functional.
Validation
nix develop -c just formatnix develop -c bun test— 72 passed, 0 failednix develop -c just lint— 0 errors; 12 pre-existing warningsnix develop -c just buildThinkingtoThoughttransition, tool status rows, approval denial, and run cancellationPackaging note
The debug macOS
.appbundled and launched successfully. The subsequent updater-archive signing step reports the expected missing localTAURI_SIGNING_PRIVATE_KEYafter the runnable app bundle has already been produced.Summary by CodeRabbit
New Features
Improvements
Tests