Skip to content

Conversation

@tomerqodo
Copy link

@tomerqodo tomerqodo commented Jan 28, 2026

Benchmark PR from agentic-review-benchmarks#4

Summary by CodeRabbit

Release Notes

  • New Features
    • Comments now display post feature images in the list view.
    • Added expandable comment content with show more/less functionality.
    • Click on author names or post titles to filter comments.
    • Enhanced metadata display showing creation timestamps, reply counts, likes, and reports with tooltips.

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

Walkthrough

Added feature_image field to Comment type and post serialization. Refactored comments list component with inline metadata, author/post filtering, expandable content with clamping logic, feature image display, and enhanced visibility controls. Updated date formatting to remove comma between day and year.

Changes

Cohort / File(s) Summary
API & Type Definitions
apps/admin-x-framework/src/api/comments.ts, ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js
Added optional feature_image field to Comment type's post object and updated post serialization to include feature_image in serialized output.
UI Component Refactoring
apps/posts/src/views/comments/components/comments-list.tsx
Introduced ExpandButton and CommentContent helpers for expandable content with clamping. Reworked comment row rendering to inline author, post, and timestamp details in single TableCell. Replaced static headers with minimal placeholders. Added Hide/Show visibility controls, feature image column rendering, author/post filtering via required onAddFilter, tooltips for metadata, and updated date formatting.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A comment's portrait now gleams with feature_image bright,
With filtering, expanding content, and metadata in sight,
Authors link to posts, each carefully arranged,
The comments-list component has beautifully changed! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the main change: a redesign of the comment moderation list layout, which is the primary focus across all modified files.

✏️ 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: 1

🤖 Fix all issues with AI agents
In `@apps/posts/src/views/comments/components/comments-list.tsx`:
- Around line 87-120: The effect in CommentContent doesn't re-run when the
comment HTML changes; update the useEffect dependency array to include item.html
so checkIfClamped runs whenever the comment content updates (and keep the
existing resize listener cleanup). Optionally, reset isExpanded (e.g.,
setIsExpanded(false)) when item.html changes to avoid carrying expanded state
between different comments.
🧹 Nitpick comments (2)
apps/posts/src/views/comments/components/comments-list.tsx (2)

200-207: Consider removing or implementing the commented-out AvatarImage code.

The commented-out AvatarImage block suggests Gravatar support was disabled temporarily. If this is intentional for the current release, consider adding a TODO comment explaining why it was disabled, or remove the dead code entirely to reduce noise.


275-317: Consider consolidating TooltipProvider instances.

Multiple TooltipProvider wrappers can be replaced with a single parent provider wrapping all tooltips, reducing component nesting and improving readability.

♻️ Suggested refactor
-                                            <div className='ml-4 flex items-center gap-3'>
-                                                <TooltipProvider>
-                                                    <Tooltip>
+                                            <TooltipProvider>
+                                                <div className='ml-4 flex items-center gap-3'>
+                                                    <Tooltip>
                                                         <TooltipTrigger asChild>
                                                             <div className={`ml-2 flex items-center gap-1 text-xs ${!item.count?.replies && 'text-muted-foreground/70'}`}>
                                                                 <LucideIcon.Reply size={16} strokeWidth={1.5} />
                                                                 <span>{formatNumber(item.count?.replies)}</span>
                                                             </div>
                                                         </TooltipTrigger>
                                                         <TooltipContent>
                                                             Replies
                                                         </TooltipContent>
                                                     </Tooltip>
-                                                </TooltipProvider>
-
-                                                <TooltipProvider>
                                                     <Tooltip>
                                                         <!-- ... likes tooltip ... -->
                                                     </Tooltip>
-                                                </TooltipProvider>
-
-                                                <TooltipProvider>
                                                     <Tooltip>
                                                         <!-- ... reports tooltip ... -->
                                                     </Tooltip>
-                                                </TooltipProvider>
-                                            </div>
+                                                </div>
+                                            </TooltipProvider>
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8281bbf and 5a73fdc.

⛔ Files ignored due to path filters (2)
  • ghost/core/test/e2e-api/admin/__snapshots__/activity-feed.test.js.snap is excluded by !**/*.snap
  • ghost/core/test/e2e-api/admin/__snapshots__/comments.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (3)
  • apps/admin-x-framework/src/api/comments.ts
  • apps/posts/src/views/comments/components/comments-list.tsx
  • ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js
🧰 Additional context used
🧬 Code graph analysis (1)
apps/posts/src/views/comments/components/comments-list.tsx (6)
apps/admin-x-framework/src/api/comments.ts (1)
  • Comment (8-35)
ghost/core/core/server/models/comment.js (1)
  • Comment (28-359)
apps/shade/src/components/ui/avatar.tsx (2)
  • Avatar (48-48)
  • AvatarFallback (48-48)
apps/shade/src/components/ui/tooltip.tsx (4)
  • TooltipProvider (33-33)
  • Tooltip (33-33)
  • TooltipTrigger (33-33)
  • TooltipContent (33-33)
apps/comments-ui/src/utils/admin-api.ts (2)
  • hideComment (59-61)
  • showComment (62-64)
apps/shade/src/components/ui/dropdown-menu.tsx (4)
  • DropdownMenu (189-189)
  • DropdownMenuTrigger (190-190)
  • DropdownMenuContent (191-191)
  • DropdownMenuItem (192-192)
🪛 ast-grep (0.40.5)
apps/posts/src/views/comments/components/comments-list.tsx

[warning] 109-109: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
apps/posts/src/views/comments/components/comments-list.tsx

[error] 110-110: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🔇 Additional comments (6)
ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js (1)

34-40: LGTM!

The addition of feature_image to postFields correctly enables the serializer to include post thumbnails in the comment response, aligning with the corresponding TypeScript type update and UI implementation.

apps/posts/src/views/comments/components/comments-list.tsx (4)

60-71: LGTM!

The formatDate function correctly uses Intl.DateTimeFormat for locale-aware formatting and the regex cleanly removes the comma between day and year as intended.


73-85: LGTM!

Clean, focused component with appropriate styling and icon usage for the expand/collapse toggle.


350-358: LGTM!

Good implementation with proper alt text for accessibility, responsive visibility, and consistent image sizing with object-cover.


263-274: LGTM!

Clean conditional rendering for hide/show actions. The lack of confirmation dialog is appropriate since these are easily reversible operations, unlike the delete action which correctly uses an AlertDialog.

apps/admin-x-framework/src/api/comments.ts (1)

23-29: LGTM!

The feature_image addition to the post type correctly aligns with the backend serializer changes and is appropriately marked as optional.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +87 to 120
function CommentContent({item}: {item: Comment}) {
const contentRef = useRef<HTMLDivElement>(null)
const [isClamped, setIsClamped] = useState(false)
const [isExpanded, setIsExpanded] = useState(false)

useEffect(() => {
const checkIfClamped = () => {
if (contentRef.current) {
// Check if the content is clamped by comparing scrollHeight with clientHeight
setIsClamped(contentRef.current.scrollHeight > contentRef.current.clientHeight);
}
};

checkIfClamped();
// Recheck on window resize
window.addEventListener('resize', checkIfClamped);
return () => window.removeEventListener('resize', checkIfClamped);
}, []);

return (
<div className={`mt-1 flex flex-col gap-2`}>
<div className="flex max-w-[720px] flex-col items-start">
<div
dangerouslySetInnerHTML={{__html: item.html || ''}}
ref={contentRef}
className={`prose flex-1 text-base leading-[1.45em] ${isExpanded ? '-mb-1 [&_p]:mb-[0.85em]' : 'line-clamp-2 [&_*]:m-0 [&_*]:inline'} ${item.status === 'hidden' && 'text-muted-foreground'}`}
/>
{isClamped && (
<ExpandButton expanded={isExpanded} onClick={() => setIsExpanded(!isExpanded)} />
)}
</div>
</div>
);
}
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:

#!/bin/bash
# Search for HTML sanitization patterns
echo "=== Searching for HTML sanitization keywords ==="
rg -i 'sanitize|purify|domPurify|xss|htmlsafe' --type ts --type tsx --type js -n

echo ""
echo "=== Searching for Comment type definition ==="
fd -e ts -e tsx | xargs grep -l "type Comment\|interface Comment" | head -20

echo ""
echo "=== Checking for comment processing/API logic ==="
fd -e ts -e tsx -path "*/comment*" | head -20

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 765


🏁 Script executed:

#!/bin/bash
# Search for where item.html is populated/used
echo "=== Searching for 'item.html' usage ==="
rg "item\.html" --type ts --type tsx -n -B2 -A2

echo ""
echo "=== Searching for Comment interface/type ==="
rg "interface Comment|type Comment" --type ts --type tsx -n -A5

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 242


🏁 Script executed:

#!/bin/bash
# Search for HTML sanitization patterns (without type filter)
echo "=== Searching for HTML sanitization keywords ==="
rg -i 'sanitize|purify|domPurify|xss' -n | head -40

echo ""
echo "=== Searching for Comment type/interface definition ==="
rg "type Comment|interface Comment" -n -A8 | head -60

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 10349


🏁 Script executed:

#!/bin/bash
# Check the specific files that likely contain Comment definition
echo "=== Checking comments API file ==="
cat -n apps/admin-x-framework/src/api/comments.ts | head -100

echo ""
echo "=== Checking comments-list.tsx for imports and item.html usage ==="
cat -n apps/posts/src/views/comments/components/comments-list.tsx | head -50

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 5032


🏁 Script executed:

#!/bin/bash
# Look for server-side comment processing/sanitization in ghost core
echo "=== Searching for comment HTML processing in Ghost core ==="
find ghost/core -type f -name "*.js" | xargs grep -l "comment" | grep -E "(model|service|filter|sanitize)" | head -10

echo ""
echo "=== Check if comments are sanitized in the API response ==="
find ghost/core -type f -name "*.js" | xargs grep -l "html" | xargs grep -l "comment" | head -10

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 1444


🏁 Script executed:

#!/bin/bash
# Search for comment model/processing in Ghost
echo "=== Searching for comment model/serializer ==="
find ghost/core -type f -name "*comment*" | head -20

echo ""
echo "=== Search for where comment.html gets set ==="
find ghost/core -type f -name "*.js" | xargs grep -n "comment.*html\|html.*comment" | head -20

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 4388


🏁 Script executed:

#!/bin/bash
# Check the output mapper for comments - this is where HTML gets prepared for API response
echo "=== Checking comments output mapper ==="
cat -n ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 3691


🏁 Script executed:

#!/bin/bash
# Check the CommentsService where html is being set
echo "=== Checking CommentsService for HTML sanitization ==="
cat -n ghost/core/core/server/services/comments/CommentsService.js | head -300

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 11495


🏁 Script executed:

#!/bin/bash
# Continue searching CommentsService for HTML handling
echo "=== More of CommentsService (lines 300-450) ==="
sed -n '300,450p' ghost/core/core/server/services/comments/CommentsService.js

echo ""
echo "=== Search for sanitize-html usage with comments ==="
rg "sanitize" ghost/core/core/server/services/comments/ -A3 -B3

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 5107


🏁 Script executed:

#!/bin/bash
# Check the Comment model for hooks/lifecycle events
echo "=== Checking Comment model definition ==="
cat -n ghost/core/core/server/models/comment.js

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 16529


🏁 Script executed:

#!/bin/bash
# Check how the component handles re-renders when item prop changes
echo "=== Full CommentContent component ==="
sed -n '87,120p' apps/posts/src/views/comments/components/comments-list.tsx

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 1553


Add item.html to the useEffect dependency array to ensure the clamping check re-runs when comment content changes.

The useEffect hook only runs once on component mount due to the empty dependency array. When the item prop changes (e.g., displaying a different comment), React re-renders the component and updates the DOM via dangerouslySetInnerHTML, but the effect doesn't re-run to measure the new content's dimensions. This means the clamping state won't update correctly when switching between comments.

Suggested fix
     useEffect(() => {
         const checkIfClamped = () => {
             if (contentRef.current) {
                 // Check if the content is clamped by comparing scrollHeight with clientHeight
                 setIsClamped(contentRef.current.scrollHeight > contentRef.current.clientHeight);
             }
         };

         checkIfClamped();
         // Recheck on window resize
         window.addEventListener('resize', checkIfClamped);
         return () => window.removeEventListener('resize', checkIfClamped);
-    }, []);
+    }, [item.html]);
📝 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
function CommentContent({item}: {item: Comment}) {
const contentRef = useRef<HTMLDivElement>(null)
const [isClamped, setIsClamped] = useState(false)
const [isExpanded, setIsExpanded] = useState(false)
useEffect(() => {
const checkIfClamped = () => {
if (contentRef.current) {
// Check if the content is clamped by comparing scrollHeight with clientHeight
setIsClamped(contentRef.current.scrollHeight > contentRef.current.clientHeight);
}
};
checkIfClamped();
// Recheck on window resize
window.addEventListener('resize', checkIfClamped);
return () => window.removeEventListener('resize', checkIfClamped);
}, []);
return (
<div className={`mt-1 flex flex-col gap-2`}>
<div className="flex max-w-[720px] flex-col items-start">
<div
dangerouslySetInnerHTML={{__html: item.html || ''}}
ref={contentRef}
className={`prose flex-1 text-base leading-[1.45em] ${isExpanded ? '-mb-1 [&_p]:mb-[0.85em]' : 'line-clamp-2 [&_*]:m-0 [&_*]:inline'} ${item.status === 'hidden' && 'text-muted-foreground'}`}
/>
{isClamped && (
<ExpandButton expanded={isExpanded} onClick={() => setIsExpanded(!isExpanded)} />
)}
</div>
</div>
);
}
function CommentContent({item}: {item: Comment}) {
const contentRef = useRef<HTMLDivElement>(null)
const [isClamped, setIsClamped] = useState(false)
const [isExpanded, setIsExpanded] = useState(false)
useEffect(() => {
const checkIfClamped = () => {
if (contentRef.current) {
// Check if the content is clamped by comparing scrollHeight with clientHeight
setIsClamped(contentRef.current.scrollHeight > contentRef.current.clientHeight);
}
};
checkIfClamped();
// Recheck on window resize
window.addEventListener('resize', checkIfClamped);
return () => window.removeEventListener('resize', checkIfClamped);
}, [item.html]);
return (
<div className={`mt-1 flex flex-col gap-2`}>
<div className="flex max-w-[720px] flex-col items-start">
<div
dangerouslySetInnerHTML={{__html: item.html || ''}}
ref={contentRef}
className={`prose flex-1 text-base leading-[1.45em] ${isExpanded ? '-mb-1 [&_p]:mb-[0.85em]' : 'line-clamp-2 [&_*]:m-0 [&_*]:inline'} ${item.status === 'hidden' && 'text-muted-foreground'}`}
/>
{isClamped && (
<ExpandButton expanded={isExpanded} onClick={() => setIsExpanded(!isExpanded)} />
)}
</div>
</div>
);
}
🧰 Tools
🪛 ast-grep (0.40.5)

[warning] 109-109: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)

[error] 110-110: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🤖 Prompt for AI Agents
In `@apps/posts/src/views/comments/components/comments-list.tsx` around lines 87 -
120, The effect in CommentContent doesn't re-run when the comment HTML changes;
update the useEffect dependency array to include item.html so checkIfClamped
runs whenever the comment content updates (and keep the existing resize listener
cleanup). Optionally, reset isExpanded (e.g., setIsExpanded(false)) when
item.html changes to avoid carrying expanded state between different comments.

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.

4 participants