Skip to content

Conversation

@rossmanko
Copy link
Contributor

@rossmanko rossmanko commented Jan 28, 2026

  • Extract SharedMessagePartHandler into dedicated component for better maintainability
  • Fix terminal output extraction to handle nested result object structure (was showing [object Object] due to incorrect type assumption)
  • Add SharedTodoBlock component for rendering todo lists in shared chats
  • Add support for additional tool types: http_request, update_memory, match (glob/grep)
  • Add proper sidebar integration for viewing tool execution details
  • Refactor SharedMessages.tsx to use new component architecture
  • Update tests to match new action text in tool blocks

Summary by CodeRabbit

Release Notes

  • Improvements
    • Updated status text labels for better clarity ("Successfully wrote," "Successfully edited," "Searched web")
    • Enhanced accessibility with improved icon labeling across message components
    • Refined todo block display and rendering for improved usability

✏️ Tip: You can customize this high-level summary in your review settings.

- Extract SharedMessagePartHandler into dedicated component for better maintainability
- Fix terminal output extraction to handle nested result object structure
  (was showing [object Object] due to incorrect type assumption)
- Add SharedTodoBlock component for rendering todo lists in shared chats
- Add support for additional tool types: http_request, update_memory, match (glob/grep)
- Add proper sidebar integration for viewing tool execution details
- Refactor SharedMessages.tsx to use new component architecture
- Update tests to match new action text in tool blocks

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Jan 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
hackerai Ready Ready Preview, Comment Jan 28, 2026 11:07pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

Refactors SharedMessages component by extracting inline message part rendering logic into a new SharedMessagePartHandler component that centralizes handling for text, tools, and content types. Introduces SharedTodoBlock for standalone todo rendering. Updates test expectations for status text labels.

Changes

Cohort / File(s) Summary
Message rendering refactoring
app/share/[shareId]/SharedMessages.tsx
Removes inline renderPart logic and useSharedChatContext import; delegates all part rendering to new SharedMessagePartHandler. Consolidates file/image placeholder rendering and updates icons with aria-hidden attributes for accessibility.
Test expectations update
app/share/[shareId]/__tests__/SharedMessages.test.tsx
Updates UI text assertions to reflect new status labels: "Wrote" → "Successfully wrote", "Edited" → "Successfully edited", "Searched" → "Searched web".
New part rendering component
app/share/[shareId]/components/SharedMessagePartHandler.tsx
Introduces comprehensive SharedMessagePartHandler component with specialized renderers for diverse message part types: text, reasoning, summarization, file/image placeholders, terminal/tool outputs, Python, web search, URL opening, glob/grep matching, file listings, todos, memory actions, and HTTP requests. Handles sidebar interactions and keyboard support.
New todo block component
app/share/[shareId]/components/SharedTodoBlock.tsx
Introduces standalone SharedTodoBlock component for rendering simplified todo lists with expand/collapse state, view-all toggle, status grouping, and in-progress task tracking.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #75: Modifies file-related message parts rendering and introduces file download logic, complementing the new SharedMessagePartHandler's file placeholder and file tool rendering.
  • PR #160: Implements "match" (glob/grep) tool functionality, which SharedMessagePartHandler now provides UI rendering for.
  • PR #187: Removes match tool implementation/export, creating a potential conflict with this PR's addition of match tool rendering in SharedMessagePartHandler.

Poem

🐰✨ A handler hops where parts once scattered lay,
Uniting text and tools in one bright way.
Todo blocks spring forth, todos take their place,
Consolidation dances—a refactored space! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly addresses the main changes: extracting tool handlers into a new component and adding support for missing tool types (http_request, update_memory, match). It accurately summarizes the primary objectives.
Docstring Coverage ✅ Passed Docstring coverage is 92.86% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@app/share/`[shareId]/components/SharedTodoBlock.tsx:
- Around line 148-156: The SharedTodoBlock renders an actual <button> (toggled
by showAllTodos via handleToggleViewAll) inside a parent Button component,
creating invalid nested interactive elements; fix by removing the inner <button>
and either (a) replace it with a non-interactive element (e.g., a <span> or
<div> with role="button" or onClick handler) styled like a button, or (b) move
the toggle control out of the parent Button so handleToggleViewAll and the
showAllTodos text are rendered as a sibling element; update the JSX that checks
isExpanded && headerContent.showViewAll to use the non-nested element and ensure
it keeps the same styling and onClick behavior.

In `@app/share/`[shareId]/SharedMessages.tsx:
- Around line 56-66: The frontend separates file/image placeholders into
filePlaceholders and otherParts but only treats filePlaceholders specially for
user messages (see isUser check) causing assistant placeholders to stay in
otherParts and render right-aligned inside a left-aligned assistant bubble via
SharedMessagePartHandler; update the rendering logic in SharedMessages (use the
same extraction and special rendering for filePlaceholders regardless of message
role) by moving the filePlaceholders handling out of the isUser-only branch (or
modify the isUser condition to include assistant placeholders) so placeholders
are extracted from message.parts and rendered outside the bubble consistently
for both user and assistant messages.
🧹 Nitpick comments (5)
app/share/[shareId]/components/SharedTodoBlock.tsx (3)

9-12: Unused blockId prop.

The blockId prop is declared in the interface but never used within the component. Either remove it from the props or use it (e.g., for data-testid or as a key prefix).

♻️ Proposed fix to remove unused prop
 interface SharedTodoBlockProps {
   todos: Todo[];
-  blockId: string;
 }

-export const SharedTodoBlock = ({ todos, blockId }: SharedTodoBlockProps) => {
+export const SharedTodoBlock = ({ todos }: SharedTodoBlockProps) => {

79-127: Consider memoizing getVisibleTodos.

Unlike todoData and headerContent, getVisibleTodos is a plain function that gets recreated and executed on every render. For consistency and to avoid unnecessary recalculations, consider converting it to a useMemo hook.

♻️ Proposed refactor
-  const getVisibleTodos = () => {
+  const visibleTodos = useMemo(() => {
     const { hasProgress, done, currentInProgress, byStatus } = todoData;

     if (!hasProgress || done === 0) {
       return todos;
     }

     if (showAllTodos) {
       return todos;
     }

     // Show collapsed view: last completed + current in-progress
-    const visibleTodos: Todo[] = [];
+    const result: Todo[] = [];
     // ... rest of logic using result instead of visibleTodos
-    return visibleTodos;
-  };
+    return result;
+  }, [todoData, showAllTodos, todos]);

Then update the render call:

-            {getVisibleTodos().map((todo) => (
+            {visibleTodos.map((todo) => (

30-33: Redundant condition in getVisibleTodos.

The condition !hasProgress || done === 0 on line 82 is redundant because hasProgress is defined as done > 0 (line 33). This means !hasProgress is equivalent to done === 0, making the entire condition just done === 0.

♻️ Simplify the condition
-    if (!hasProgress || done === 0) {
+    if (done === 0) {
       return todos;
     }

Also applies to: 82-84

app/share/[shareId]/components/SharedMessagePartHandler.tsx (1)

33-42: Consider adding data field to MessagePart interface.

The data-summarization renderer (line 711) accesses part.data via type assertion (part as any).data. Adding an optional data field to the interface would improve type safety.

♻️ Proposed interface update
 interface MessagePart {
   type: string;
   text?: string;
   placeholder?: boolean;
   state?: string;
   input?: any;
   output?: any;
   toolCallId?: string;
   errorText?: string;
+  data?: { status?: string; message?: string };
 }

Then update the renderer:

-function renderSummarizationPart(part: MessagePart, idx: number) {
-  const data = (part as any).data as { status?: string; message?: string };
+function renderSummarizationPart(part: MessagePart, idx: number) {
+  const data = part.data;
app/share/[shareId]/__tests__/SharedMessages.test.tsx (1)

289-309: Consider adding test coverage for SharedTodoBlock with actual todos.

The current test only covers the fallback case when no currentTodos are present (rendering "Updated todos"). Consider adding a test case where currentTodos is populated to verify SharedTodoBlock renders correctly with todo items.

📝 Example test case
it("should render todo block with todos when currentTodos provided", () => {
  const messages = [
    {
      id: "1",
      role: "assistant" as const,
      parts: [
        {
          type: "tool-todo_write",
          state: "output-available",
          output: {
            currentTodos: [
              { id: "t1", content: "First task", status: "completed" },
              { id: "t2", content: "Second task", status: "in_progress" },
            ],
          },
        },
      ],
      update_time: mockShareDate,
    },
  ];

  renderWithContext(
    <SharedMessages messages={messages} shareDate={mockShareDate} />,
  );
  // Verify SharedTodoBlock renders (e.g., check for progress text or expand button)
  expect(screen.getByRole("button", { name: /todos/i })).toBeInTheDocument();
});

Comment on lines +148 to +156
{isExpanded && headerContent.showViewAll && (
<button
type="button"
onClick={handleToggleViewAll}
className="text-[12px] text-muted-foreground/70 hover:text-muted-foreground transition-colors cursor-pointer p-1 ml-2 bg-transparent border-none"
>
{showAllTodos ? "Hide" : "View All"}
</button>
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Nested interactive elements: button inside Button.

The "View All" <button> is rendered inside the <Button> component, creating invalid HTML (button-in-button nesting). This causes accessibility issues and unpredictable click behavior. Consider restructuring to avoid nesting or moving the toggle outside the header button.

🛠️ Proposed fix

One approach is to use a non-interactive element with click handling, or restructure the layout:

             {isExpanded && headerContent.showViewAll && (
-              <button
-                type="button"
+              <span
+                role="button"
+                tabIndex={0}
                 onClick={handleToggleViewAll}
+                onKeyDown={(e) => {
+                  if (e.key === "Enter" || e.key === " ") {
+                    handleToggleViewAll(e);
+                  }
+                }}
                 className="text-[12px] text-muted-foreground/70 hover:text-muted-foreground transition-colors cursor-pointer p-1 ml-2 bg-transparent border-none"
               >
                 {showAllTodos ? "Hide" : "View All"}
-              </button>
+              </span>
             )}

Alternatively, move the "View All" toggle outside the <Button> entirely.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{isExpanded && headerContent.showViewAll && (
<button
type="button"
onClick={handleToggleViewAll}
className="text-[12px] text-muted-foreground/70 hover:text-muted-foreground transition-colors cursor-pointer p-1 ml-2 bg-transparent border-none"
>
{showAllTodos ? "Hide" : "View All"}
</button>
)}
{isExpanded && headerContent.showViewAll && (
<span
role="button"
tabIndex={0}
onClick={handleToggleViewAll}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
handleToggleViewAll(e);
}
}}
className="text-[12px] text-muted-foreground/70 hover:text-muted-foreground transition-colors cursor-pointer p-1 ml-2 bg-transparent border-none"
>
{showAllTodos ? "Hide" : "View All"}
</span>
)}
🤖 Prompt for AI Agents
In `@app/share/`[shareId]/components/SharedTodoBlock.tsx around lines 148 - 156,
The SharedTodoBlock renders an actual <button> (toggled by showAllTodos via
handleToggleViewAll) inside a parent Button component, creating invalid nested
interactive elements; fix by removing the inner <button> and either (a) replace
it with a non-interactive element (e.g., a <span> or <div> with role="button" or
onClick handler) styled like a button, or (b) move the toggle control out of the
parent Button so handleToggleViewAll and the showAllTodos text are rendered as a
sibling element; update the JSX that checks isExpanded &&
headerContent.showViewAll to use the non-nested element and ensure it keeps the
same styling and onClick behavior.

Comment on lines 56 to 66
const filePlaceholders = message.parts.filter(
(part) =>
(part.type === "file" || part.type === "image") && part.placeholder,
(part.type === "file" || part.type === "image") && part.placeholder
);
const otherParts = message.parts.filter(
(part) =>
!(
(part.type === "file" || part.type === "image") &&
part.placeholder
),
)
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "SharedMessages.tsx" -type f

Repository: hackerai-tech/hackerai

Length of output: 106


🏁 Script executed:

head -100 app/share/\[shareId\]/SharedMessages.tsx

Repository: hackerai-tech/hackerai

Length of output: 3143


🏁 Script executed:

sed -n '50,100p' app/share/\[shareId\]/SharedMessages.tsx

Repository: hackerai-tech/hackerai

Length of output: 1990


🏁 Script executed:

rg -n "SharedMessagePartHandler" app/share/\[shareId\]/ -A 20

Repository: hackerai-tech/hackerai

Length of output: 6261


🏁 Script executed:

cat app/share/\[shareId\]/components/SharedMessagePartHandler.tsx

Repository: hackerai-tech/hackerai

Length of output: 20136


🏁 Script executed:

rg -n "part.type.*file|part.type.*image" app/share/\[shareId\]/components/SharedMessagePartHandler.tsx -A 10

Repository: hackerai-tech/hackerai

Length of output: 3233


🏁 Script executed:

rg -n "placeholder.*true|placeholder:" . --type=ts --type=tsx -A 3 -B 3 | head -100

Repository: hackerai-tech/hackerai

Length of output: 93


🏁 Script executed:

# Search for message part creation and placeholder handling
rg -n "parts.*placeholder|placeholder.*parts" . --type=ts --type=tsx -B 2 -A 2

Repository: hackerai-tech/hackerai

Length of output: 93


🏁 Script executed:

# Check if there's any evidence of assistant messages with file/image placeholders
rg -n "assistant.*file|assistant.*image" . --type=ts --type=tsx -B 3 -A 3 | head -80

Repository: hackerai-tech/hackerai

Length of output: 93


🏁 Script executed:

rg -n "placeholder.*true" . -A 3 -B 3 | head -150

Repository: hackerai-tech/hackerai

Length of output: 2387


🏁 Script executed:

# Search for where message parts are created
rg -n "parts.*push|parts.*\=" . -B 2 -A 2 -t ts | grep -A 5 -B 5 "placeholder" | head -100

Repository: hackerai-tech/hackerai

Length of output: 172


🏁 Script executed:

# Look at the message structure and where it's populated from API
rg -n "role.*assistant|assistant.*message" . -t ts -A 5 | head -80

Repository: hackerai-tech/hackerai

Length of output: 4576


🏁 Script executed:

sed -n '1220,1260p' ./convex/messages.ts

Repository: hackerai-tech/hackerai

Length of output: 1536


🏁 Script executed:

# Also check if there are any other places placeholders are set
rg -n "placeholder\s*:" . -t ts -B 5 -A 2

Repository: hackerai-tech/hackerai

Length of output: 12224


🏁 Script executed:

sed -n '75,140p' app/share/\[shareId\]/SharedMessages.tsx

Repository: hackerai-tech/hackerai

Length of output: 2753


Address placeholder rendering inconsistency for non-user messages.

File/image placeholders are converted to simple indicators by the backend for all message roles. However, the frontend separates and renders them specially (outside the message bubble) only for user messages (isUser check on line 81). For non-user (assistant) messages, placeholders remain in otherParts and are passed to SharedMessagePartHandler, which renders them with right-aligned styling inside the left-aligned assistant message bubble, creating a visual misalignment. Either extract and render assistant placeholders separately like user messages, or confirm this behavior is intentional.

🤖 Prompt for AI Agents
In `@app/share/`[shareId]/SharedMessages.tsx around lines 56 - 66, The frontend
separates file/image placeholders into filePlaceholders and otherParts but only
treats filePlaceholders specially for user messages (see isUser check) causing
assistant placeholders to stay in otherParts and render right-aligned inside a
left-aligned assistant bubble via SharedMessagePartHandler; update the rendering
logic in SharedMessages (use the same extraction and special rendering for
filePlaceholders regardless of message role) by moving the filePlaceholders
handling out of the isUser-only branch (or modify the isUser condition to
include assistant placeholders) so placeholders are extracted from message.parts
and rendered outside the bubble consistently for both user and assistant
messages.

@rossmanko rossmanko merged commit cc5adfc into main Jan 28, 2026
4 checks passed
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