feat(activity-feed-v2): improve comment posting UX#4591
Conversation
Mention popover (V2): - Open the mention popover on @ before any character is typed - Show the start prompt while empty and "No users found" once the user types - Skip the API round-trip on empty or whitespace-only queries Comment text (V1 + V2): - Trim leading and trailing whitespace from comment text before sending to the API. Inner whitespace is preserved. V1 post button: - Disable the post button when the editor is empty or whitespace-only, matching the validation already enforced on submit. V2 auto-scroll after posting: - After a successful post, scroll to the new item using a snapshot of ids taken at post time, so a concurrent push from another user does not hijack the viewport. - Await onCommentCreate so rejections are logged and do not arm the scroll for an unrelated update.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughWhitespace trimming added to comment serialization and utilities, post button is disabled for empty editor content, ActivityFeedV2 trims mention queries, adds localized empty-state rendering and styling, switches posting to trimmed serialization, snapshots feed items to scroll only to newly-created user posts, and expands tests accordingly. ChangesComment trimming and post button UX
Sequence Diagram(s)sequenceDiagram
participant User
participant Editor as ActivityFeedV2 Editor
participant Query as Query Trim/Fetch
participant Selector as User Selector
participant Empty as renderEmpty UI
User->>Editor: Focus mention input
Editor->>Query: User types query text
Query->>Query: Trim query
alt Query is empty or whitespace
Query->>Empty: Show "start mention" state
Empty-->>Selector: Render localized message
else Query is non-empty
Query->>Selector: Call getMentionAsync(trimmed)
Selector->>Selector: Fetch user results
alt Results found
Selector-->>Editor: Display user list
else No results
Selector->>Empty: Show "no users found" state
Empty-->>Editor: Render localized message
end
end
sequenceDiagram
participant User
participant Editor as handleCommentPost
participant Serialize as serializeEditorContent
participant API as Comment API
participant State as feedItems
participant Scroll as scrollEffect
User->>Editor: Click post button
Editor->>Serialize: Serialize editor content
Serialize-->>Editor: Return trimmed text + mention flag
alt Text is empty or whitespace
Editor->>User: Skip post (text empty)
else Text is non-empty
Editor->>API: Create comment
API-->>State: feedItems updates
Editor->>Editor: Store snapshot of known ids
State->>Scroll: Detect new items
Scroll->>Scroll: Find first item not in snapshot
Scroll->>Scroll: Call scrollTo(foundItemId)
alt scrollTo succeeds
Scroll-->>User: Scroll to new user comment
else scrollTo fails
Scroll->>Scroll: Retry once
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/elements/content-sidebar/activity-feed/comment-form/__tests__/CommentForm.test.js (1)
173-187: ⚡ Quick winAdd an explicit whitespace-only test for post disablement.
Current coverage checks empty and non-whitespace text, but not
" \n\t"input, which is the trim-dependent edge this change relies on.Proposed test addition
describe('post button disabled state', () => { const findControls = wrapper => wrapper.find('CommentInputControls'); test('should pass isDisabled=true to the controls when the editor is empty', () => { const wrapper = getWrapper({ isOpen: true }); expect(findControls(wrapper).prop('isDisabled')).toBe(true); }); + test('should pass isDisabled=true to the controls when the editor has only whitespace', () => { + const wrapper = getWrapper({ isOpen: true, tagged_message: ' \n\t ' }); + + expect(findControls(wrapper).prop('isDisabled')).toBe(true); + }); + test('should pass isDisabled=false to the controls when the editor has non-whitespace content', () => { const wrapper = getWrapper({ isOpen: true, tagged_message: 'hello' }); expect(findControls(wrapper).prop('isDisabled')).toBe(false); }); });🤖 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 `@src/elements/content-sidebar/activity-feed/comment-form/__tests__/CommentForm.test.js` around lines 173 - 187, The tests for post button disabled state are missing a case for whitespace-only editor content; add a test inside the existing "post button disabled state" describe block that uses getWrapper({ isOpen: true, tagged_message: ' \n\t' }) and asserts findControls(wrapper).prop('isDisabled') is true so the CommentInputControls receives isDisabled=true for trim-only input. Use the same helper findControls and test naming style as the other two tests to keep consistency.
🤖 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 `@src/elements/content-sidebar/activity-feed-v2/ActivityFeedV2.tsx`:
- Around line 265-271: The current logic uses filteredItems.find(item =>
!knownIds.has(item.id)) which can pick a concurrent insert; instead locate the
exact pending post before scrolling: use the pending-post identity (e.g. a
pendingPostId or pendingPostRef that tracks the just-submitted comment) to find
the matching item in filteredItems (e.g. const newItem = filteredItems.find(item
=> item.id === pendingPostIdRef.current)); only call
scrollHandle.scrollTo(newItem.id) if that exact match exists, and then clear
knownIdsBeforePostRef.current as before; if no pending-post id is available, do
not fall back to “first unknown id.”
---
Nitpick comments:
In
`@src/elements/content-sidebar/activity-feed/comment-form/__tests__/CommentForm.test.js`:
- Around line 173-187: The tests for post button disabled state are missing a
case for whitespace-only editor content; add a test inside the existing "post
button disabled state" describe block that uses getWrapper({ isOpen: true,
tagged_message: ' \n\t' }) and asserts
findControls(wrapper).prop('isDisabled') is true so the CommentInputControls
receives isDisabled=true for trim-only input. Use the same helper findControls
and test naming style as the other two tests to keep consistency.
🪄 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: 77e3b531-7d29-432c-b60f-b74dd432b757
📒 Files selected for processing (11)
src/components/form-elements/draft-js-mention-selector/__tests__/utils.test.jssrc/components/form-elements/draft-js-mention-selector/utils.jssrc/elements/content-sidebar/activity-feed-v2/ActivityFeedV2.scsssrc/elements/content-sidebar/activity-feed-v2/ActivityFeedV2.tsxsrc/elements/content-sidebar/activity-feed-v2/__tests__/ActivityFeedV2.test.tsxsrc/elements/content-sidebar/activity-feed-v2/__tests__/helpers.test.tssrc/elements/content-sidebar/activity-feed-v2/helpers.tssrc/elements/content-sidebar/activity-feed/comment-form/CommentForm.jssrc/elements/content-sidebar/activity-feed/comment-form/CommentFormControls.jssrc/elements/content-sidebar/activity-feed/comment-form/__tests__/CommentForm.test.jssrc/elements/content-sidebar/activity-feed/comment-form/__tests__/CommentFormControls.test.js
| React.useEffect(() => { | ||
| const knownIds = knownIdsBeforePostRef.current; | ||
| if (!knownIds || !scrollHandle) return; | ||
| const newItem = filteredItems.find(item => !knownIds.has(item.id)); | ||
| if (!newItem) return; | ||
| if (scrollHandle.scrollTo(newItem.id)) { | ||
| knownIdsBeforePostRef.current = null; |
There was a problem hiding this comment.
Don't scroll to the first unseen item here.
find(item => !knownIds.has(item.id)) still picks whichever concurrent insert sorts first. If another user's update lands ahead of the comment that was just posted, the viewport will jump to the wrong row again. Match against a stronger identity for the pending post before calling scrollTo instead of using “first unknown id” as the selector.
🤖 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 `@src/elements/content-sidebar/activity-feed-v2/ActivityFeedV2.tsx` around
lines 265 - 271, The current logic uses filteredItems.find(item =>
!knownIds.has(item.id)) which can pick a concurrent insert; instead locate the
exact pending post before scrolling: use the pending-post identity (e.g. a
pendingPostId or pendingPostRef that tracks the just-submitted comment) to find
the matching item in filteredItems (e.g. const newItem = filteredItems.find(item
=> item.id === pendingPostIdRef.current)); only call
scrollHandle.scrollTo(newItem.id) if that exact match exists, and then clear
knownIdsBeforePostRef.current as before; if no pending-post id is available, do
not fall back to “first unknown id.”
commentEditorState is initialized in the constructor and is never undefined in production. The optional chain was a workaround for a test that mocked EditorState.moveFocusToEnd globally and leaked the mock across tests. Restore the original after each test instead.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/elements/content-sidebar/activity-feed/comment-form/__tests__/CommentForm.test.js (1)
185-199: ⚡ Quick winAdd an explicit whitespace-only disabled test case.
This suite should also assert
isDisabled=truefortagged_messagelike' \n\t 'to directly lock the trim-based behavior promised by this PR.✅ Suggested test addition
describe('post button disabled state', () => { const findControls = wrapper => wrapper.find('CommentInputControls'); test('should pass isDisabled=true to the controls when the editor is empty', () => { const wrapper = getWrapper({ isOpen: true }); expect(findControls(wrapper).prop('isDisabled')).toBe(true); }); + test('should pass isDisabled=true to the controls when the editor has only whitespace', () => { + const wrapper = getWrapper({ isOpen: true, tagged_message: ' \n\t ' }); + + expect(findControls(wrapper).prop('isDisabled')).toBe(true); + }); + test('should pass isDisabled=false to the controls when the editor has non-whitespace content', () => { const wrapper = getWrapper({ isOpen: true, tagged_message: 'hello' }); expect(findControls(wrapper).prop('isDisabled')).toBe(false); }); });🤖 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 `@src/elements/content-sidebar/activity-feed/comment-form/__tests__/CommentForm.test.js` around lines 185 - 199, Add a new test in the "post button disabled state" suite that uses getWrapper({ isOpen: true, tagged_message: ' \n\t ' }) and asserts findControls(wrapper).prop('isDisabled') === true to cover the whitespace-only case; place it alongside the existing tests referencing CommentInputControls, findControls, getWrapper, and the isDisabled prop so the trim-based behavior is explicitly validated.
🤖 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.
Nitpick comments:
In
`@src/elements/content-sidebar/activity-feed/comment-form/__tests__/CommentForm.test.js`:
- Around line 185-199: Add a new test in the "post button disabled state" suite
that uses getWrapper({ isOpen: true, tagged_message: ' \n\t ' }) and asserts
findControls(wrapper).prop('isDisabled') === true to cover the whitespace-only
case; place it alongside the existing tests referencing CommentInputControls,
findControls, getWrapper, and the isDisabled prop so the trim-based behavior is
explicitly validated.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e0c57716-fcb5-493e-89b4-9e2fbdc03e20
📒 Files selected for processing (2)
src/elements/content-sidebar/activity-feed/comment-form/CommentForm.jssrc/elements/content-sidebar/activity-feed/comment-form/__tests__/CommentForm.test.js
The existing tests overwrote EditorState.moveFocusToEnd with a bare jest.fn() that returns undefined. Once CommentForm reads the editor state during render to compute isPostDisabled, the undefined return crashed the component. Make the mock return its input so the editor state stays valid while the spy still tracks calls.
Summary
Improves comment posting UX in the activity feed across V1 and V2.
V2 mention popover - Open the popover immediately on
@(parity with box-annotations and V1) and show V1-style copy:V1 + V2 trim - Strip leading and trailing whitespace from comment text before sending to the API. Inner whitespace is preserved.
V1 disable post button - The post button is now disabled when the editor is empty or whitespace-only, matching the form validation that already blocks submit. Visible feedback instead of a clickable button that does nothing.
V2 auto-scroll on post - After a successful post, scroll to the new item using a snapshot of feed item ids taken at post time, so a concurrent push from another user does not hijack the viewport.
onCommentCreateis awaited so rejections are logged and do not arm the scroll for an unrelated update.V1 mention selector is otherwise unchanged. The trim change to
getFormattedCommentTextis intentional and applies to all V1 comment surfaces.Test plan
@, popover opens with "Mention someone to notify them"; type a letter, popover shows matches or "No users found"yarn test --watchAll=false --testPathPattern="(activity-feed-v2|comment-form|draft-js-mention-selector)"passesSummary by CodeRabbit
New Features
Bug Fixes
Style
Tests