Skip to content

Conversation

@Alam-2U
Copy link

@Alam-2U Alam-2U commented Nov 3, 2025

Description

Implements soft delete functionality for discussion threads, responses, and comments using the is_deleted flag instead of permanently deleting records.
This enables safe deletion and restoration of discussion content while preserving existing data.


Changes Made

  • Added soft delete and restore functionality for threads, responses, and comments.
  • Introduced support to view deleted content in the Learner tab using a new Active / Deleted filter.
  • Implemented restore actions for deleted threads, responses, and comments.
  • Added deleted count tracking and support for sorting by deleted count in the Learner tab.
  • Implemented bulk delete and bulk restore actions for discussion content.
  • Ensured nested responses and comments are handled correctly during delete and restore operations.
  • Added API support to fetch deleted content for moderation workflows.

Note:
Features listed above (except soft delete itself) are available only to Staff, Admins, Moderators, and TAs.


JIRA Tickets

  • COSMO2-742Link
  • COSMO2-769Link
  • COSMO2-783Link

Related Pull Requests

@Alam-2U Alam-2U force-pushed the ealam/COMSO2-742 branch 2 times, most recently from c7fb8c8 to 3af0d15 Compare November 3, 2025 12:16
@Alam-2U Alam-2U force-pushed the ealam/COMSO2-742 branch 3 times, most recently from 1d1ac54 to 8711640 Compare November 6, 2025 06:04
@Alam-2U Alam-2U marked this pull request as ready for review December 18, 2025 09:22
Copilot AI review requested due to automatic review settings December 18, 2025 09:22
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements comprehensive soft delete functionality for discussion threads, responses, and comments, allowing content to be marked as deleted rather than permanently removed from the database. The implementation includes UI indicators for deleted content, restore capabilities, and bulk operations for managing user contributions.

Key Changes:

  • Added soft delete and restore actions with confirmation dialogs for threads, responses, and comments
  • Implemented filtering between active and deleted content with dedicated views for moderators/staff
  • Added bulk delete and restore operations for user posts at course and organization levels

Reviewed changes

Copilot reviewed 47 out of 48 changed files in this pull request and generated 26 comments.

Show a summary per file
File Description
src/index.scss Added styling for deleted content indicators, learner message badges, and submenu containers
src/discussions/utils.js Extended action permissions to support restore operations and disabled actions for deleted items
src/discussions/posts/post/messages.js Added internationalized messages for delete/restore confirmations and deleted content badges
src/discussions/posts/post/PostLink.jsx Enhanced post links to display deleted state with visual indicators and handle response/comment navigation
src/discussions/posts/post/Post.jsx Integrated restore confirmation dialogs and deleted-by attribution display
src/discussions/posts/post-filter-bar/messages.js Added filter labels for active vs deleted content status
src/discussions/posts/index.js Exported PostLink component for reuse in learner views
src/discussions/posts/data/thunks.js Implemented thread restore thunk and filter logic for active/deleted content
src/discussions/posts/data/slices.js Changed default filter to active content and added deleted view state management
src/discussions/posts/data/selectors.js Added selector for deleted view state
src/discussions/posts/data/api.js Created API endpoint for restoring deleted threads
src/discussions/posts/data/__factories__/threads.factory.js Added is_deleted field to thread factory for testing
src/discussions/posts/PostsView.test.jsx Updated tests to reflect new default "active" filter
src/discussions/posts/NoResults.jsx Fixed optional chaining for learner state access
src/discussions/post-comments/messages.js Added restore confirmation messages for responses and comments
src/discussions/post-comments/data/thunks.js Extended fetch operations to support showDeleted parameter and added restore thunk
src/discussions/post-comments/data/hooks.js Implemented deleted content visibility logic based on filter state
src/discussions/post-comments/data/api.js Added showDeleted parameter to API calls and restore comment endpoint
src/discussions/post-comments/data/__factories__/comments.factory.js Added is_deleted field to comment factory
src/discussions/post-comments/comments/comment/Reply.jsx Integrated restore functionality and deleted-by attribution for replies
src/discussions/post-comments/comments/comment/CommentHeader.jsx Added flex-wrap to header for better responsive layout
src/discussions/post-comments/comments/comment/Comment.jsx Implemented restore logic and deleted state handling for comments and responses
src/discussions/post-comments/PostCommentsView.test.jsx Updated test expectations for show_deleted parameter
src/discussions/messages.js Added restore action message and bulk operation labels
src/discussions/learners/utils.js Created learner actions menu with nested delete/restore submenus
src/discussions/learners/messages.js Added messages for deleted activity stats and restore operations
src/discussions/learners/learner/proptypes.js Extended learner shape with deleted content counts
src/discussions/learners/learner/LearnerFooter.jsx Added deleted activity indicator for staff/moderators
src/discussions/learners/learner/LearnerFilterBar.jsx Added deleted activity sorting option
src/discussions/learners/learner/LearnerCard.jsx Passed deleted stats to footer component
src/discussions/learners/learner-post-filter-bar/LearnerPostFilterBar.test.jsx Updated tests for new content status filter radiogroup
src/discussions/learners/learner-post-filter-bar/LearnerPostFilterBar.jsx Added content status filter for active/deleted distinction
src/discussions/learners/data/thunks.js Implemented deleted content fetching and restore operations
src/discussions/learners/data/slices.js Added restore action reducers and merged filter updates
src/discussions/learners/data/redux.test.jsx Added test assertion for default contentStatus
src/discussions/learners/data/api.js Created APIs for bulk restore and fetching deleted content
src/discussions/learners/LearnerPostsView.test.jsx Updated tests to handle submenu interactions
src/discussions/learners/LearnerPostsView.jsx Integrated restore confirmations and post refresh logic
src/discussions/learners/LearnerActionsDropdown.test.jsx Added submenu hover interactions to tests
src/discussions/learners/LearnerActionsDropdown.jsx Implemented nested submenu with portal rendering for delete/restore actions
src/discussions/data/thunks.js Added performRestoreThread thunk
src/discussions/data/selectors.js Updated filter logic to treat ACTIVE status as unfiltered
src/discussions/data/constants.js Added THREAD_FILTER_TYPES constants
src/discussions/common/HoverCard.jsx Disabled interactive actions for deleted content
src/discussions/common/ActionsDropdown.jsx Added disabled state handling for action items
src/data/constants.js Added RESTORE action and ACTIVE/DELETED status filter constants
src/components/FilterBar.jsx Enhanced filter bar to support separated filter sections
src/assets/undelete.svg Added restore/undelete icon SVG

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


import FilterBar from '../../../components/FilterBar';
import { PostsStatusFilter, ThreadType } from '../../../data/constants';
// import { PostsStatusFilter, ThreadType } from '../../../data/constants';
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The commented-out import statement should be removed rather than commented. If these imports are needed, they should be active; if not, remove them entirely. Commented code creates confusion and clutter in the codebase.

Suggested change
// import { PostsStatusFilter, ThreadType } from '../../../data/constants';

Copilot uses AI. Check for mistakes.
Comment on lines +590 to 618
min-width: 195px !important;
border: none !important;
outline: none !important;
}

.actions-dropdown-item:hover,
.actions-dropdown-item:focus,
.actions-dropdown-item:active {
border: none !important;
outline: none !important;
box-shadow: none !important;
}

.learner-submenu-container {
min-width: 280px;
max-width: 320px;
z-index: 1051;
border: 1px solid var(--pgn-color-light-400) !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
outline: none !important;
}

.learner-submenu-container .actions-dropdown-item {
min-width: 280px !important;
max-width: 320px !important;
white-space: normal;
text-align: left;
border: none !important;
}
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The missing semicolon on line 590 has been added, but the surrounding code shows inconsistent patterns for button states. The actions-dropdown-item has border and outline rules defined multiple times with different specificity. Consider consolidating these styles to avoid potential conflicts.

Copilot uses AI. Check for mistakes.
Comment on lines +218 to +222
).map(action => ({
...action,
// For deleted items, disable all actions except 'copy-link' and 'restore'
disabled: content.isDeleted && action.id !== 'copy-link' && action.id !== 'restore',
})), [content]);
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The action filtering logic now adds a disabled property based on isDeleted state. However, this mutates the action objects during rendering which could cause issues with memoization. Consider moving this logic into the checkPermissions or checkConditions functions instead, or creating new action objects rather than spreading and modifying existing ones.

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +121
if (result.success) {
window.location.reload();
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

After restoring a thread, the code calls window.location.reload() which causes a full page refresh. This is not ideal for user experience as it loses the current state and causes a jarring transition. Consider using Redux state updates to refresh just the thread data instead of reloading the entire page.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +20
const learnersFilter = useSelector(({ learners }) => learners?.usernameSearch);
const isFiltered = postsFiltered || (topicsFilter !== '')
|| (learnersFilter !== null) || (inContextTopicsFilter !== '');
|| (learnersFilter) || (inContextTopicsFilter !== '');
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The optional chaining learners?.usernameSearch and learners?.postFilter suggests that the learners state might be undefined in some cases. However, this defensive programming is inconsistent - other parts of the codebase access state.learners directly without null checks. Consider either making the learners reducer always defined or consistently use optional chaining throughout.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +51
const useShowDeletedContent = () => {
const { learnerUsername } = useContext(DiscussionContext);
const postFilter = useSelector(state => state.learners?.postFilter);

// Show deleted content if we're in learner view and the deleted filter is active (contentStatus)
return learnerUsername && postFilter?.contentStatus === PostsStatusFilter.DELETED;
};
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The useShowDeletedContent hook checks postFilter?.contentStatus === PostsStatusFilter.DELETED but only when in learner view. This logic is duplicated in multiple places (lines 45-51, 85-89). Consider extracting this to a shared selector or utility function to maintain consistency and avoid code duplication.

Copilot uses AI. Check for mistakes.
Comment on lines 98 to 106
const { getDeletedContent } = await import('./api');
data = await getDeletedContent(courseId, {
author,
page,
pageSize: 10,
});
} else {
// Use regular learner posts endpoint for active content
const { getUserPosts } = await import('./api');
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The code dynamically imports API functions inside the thunk (const { getDeletedContent } = await import('./api')). While lazy loading can help with bundle size, this creates inconsistency since other API calls in the same file use static imports. Either use dynamic imports consistently or use static imports for all APIs in this file to avoid confusion and ensure predictable loading behavior.

Copilot uses AI. Check for mistakes.
contentType,
} = {}) {
const params = snakeCaseObject({
authorId: author, // The backend expects author_id
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The getDeletedContent function uses authorId in the params but the JSDoc says @param {string} author. The parameter name should match the documentation. Either change the JSDoc to authorId or change the API parameter to use author consistently with other functions.

Suggested change
authorId: author, // The backend expects author_id
author, // The backend expects author

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings December 18, 2025 11:09
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 47 out of 48 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 116 to 127
const handleRestoreConfirmation = useCallback(async () => {
try {
const { performRestoreThread } = await import('../data/thunks');
const result = await dispatch(performRestoreThread(postId, courseId));
if (result.success) {
await dispatch(fetchThread(postId, courseId));
}
} catch (error) {
logError(error);
}
hideRestoreConfirmation();
}, [postId, courseId, dispatch, hideRestoreConfirmation]);
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

Inconsistent return type handling. The function performRestoreThread returns an object with success and optionally error properties, but the calling code in Post.jsx only checks result.success. If the restore fails and returns success: false, there's no user feedback about the error. Consider logging the error or displaying it to the user.

Copilot uses AI. Check for mistakes.

import FilterBar from '../../../components/FilterBar';
import { PostsStatusFilter, ThreadType } from '../../../data/constants';
// import { PostsStatusFilter, ThreadType } from '../../../data/constants';
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

Commented out import statement should be removed. The commented import is replaced by the new imports above it, so this line should be deleted to keep the code clean.

Suggested change
// import { PostsStatusFilter, ThreadType } from '../../../data/constants';

Copilot uses AI. Check for mistakes.
},
{
name: 'status',
name: 'status', // secondary status
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The inline comment 'secondary status' is potentially misleading. This comment suggests there's a distinction between primary and secondary status filters, but this isn't clearly documented elsewhere. Consider either adding more comprehensive documentation or removing the comment if it doesn't add clarity.

Copilot uses AI. Check for mistakes.
Comment on lines 97 to 103
if (filters.contentStatus === PostsStatusFilter.DELETED) {
const { getDeletedContent } = await import('./api');
data = await getDeletedContent(courseId, {
author,
page,
pageSize: 10,
});
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

Missing error handling for the dynamic import. If the import of 'getDeletedContent' fails, it will throw an unhandled error. Consider wrapping the import in a try-catch block or handling the error appropriately.

Copilot uses AI. Check for mistakes.
};

// For comments/responses, show parent thread title with arrow
const displayTitle = (type === 'response' || type === 'comment') && threadTitle ? threadTitle : title;
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The ternary operation uses threadTitle and title which may both be falsy. If both are undefined or null, displayTitle could be an empty value. Consider adding a fallback or validation to ensure a valid title is always displayed.

Copilot uses AI. Check for mistakes.
const displayTitle = (type === 'response' || type === 'comment') && threadTitle ? threadTitle : title;

// For comments/responses, navigate to the parent thread instead of the comment itself
const navigationPostId = (type === 'response' || type === 'comment') && commentThreadId ? commentThreadId : postId;
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The navigationPostId calculation could result in undefined if both commentThreadId and postId are falsy. This could lead to navigation errors or broken links. Add validation to ensure a valid post ID is always used.

Copilot uses AI. Check for mistakes.
} from '../../../../data/constants';
import {
AlertBanner, AuthorLabel, AutoSpamAlertBanner, Confirmation, EndorsedAlertBanner,
} from '../../../common';
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

Duplicate import statement. Line 24 imports from '../../../data/constants' but lines 19-20 already import from the same module. These should be combined into a single import statement to avoid duplication.

Suggested change
} from '../../../common';

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings December 19, 2025 15:19
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 46 out of 47 changed files in this pull request and generated 16 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +69 to +144
{isOpen && ReactDOM.createPortal(
<ModalPopup
onClose={onCloseModal}
positionRef={target}
isOpen={isOpen}
placement="bottom-start"
style={{ zIndex: 9998 }}
>
{actions.map(action => (
<React.Fragment key={action.id}>
<Dropdown.Item
as={Button}
variant="tertiary"
size="inline"
onClick={() => {
close();
handleActions(action.action);
}}
className="d-flex justify-content-start actions-dropdown-item"
data-testId={action.id}
<div
className="bg-white shadow d-flex flex-column mt-1"
data-testid="learner-actions-dropdown-modal-popup"
style={{ position: 'relative', zIndex: 9998 }}
>
{menuItems.map(item => (
<div
key={item.id}
className="position-relative"
onMouseEnter={() => setActiveSubmenu(item.id)}
onMouseLeave={() => setActiveSubmenu(null)}
style={{ zIndex: 2 }}
>
<Icon
src={action.icon}
className="icon-size-24"
/>
<span className="font-weight-normal ml-2">
{action.label.defaultMessage}
</span>
</Dropdown.Item>
</React.Fragment>
))}
</div>
</ModalPopup>
<Dropdown.Item
as={Button}
variant="tertiary"
size="inline"
className="d-flex justify-content-between align-items-center actions-dropdown-item"
data-testid={item.id}
>
<div className="d-flex align-items-center">
<span className="font-weight-normal">
{item.label}
</span>
</div>
<Icon
src={ChevronRight}
className="icon-size-16"
/>
</Dropdown.Item>
{activeSubmenu === item.id && (
<div
className="bg-white learner-submenu-container"
style={{
position: 'absolute',
left: '100%',
top: 0,
minWidth: 300,
maxWidth: 360,
zIndex: 9999,
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
border: '1px solid var(--pgn-color-light-400)',
overflow: 'visible',
}}
>
{item.submenu.map(subItem => (
<Dropdown.Item
key={subItem.id}
as={Button}
variant="tertiary"
size="inline"
onClick={() => handleActions(subItem.action)}
className="d-flex justify-content-start actions-dropdown-item"
data-testid={subItem.id}
>
<span className="font-weight-normal">
{subItem.label}
</span>
</Dropdown.Item>
))}
</div>
)}
</div>
))}
</div>
</ModalPopup>,
document.body,
)}
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

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

ReactDOM.createPortal is used to render the modal popup into document.body, but the portal is only created when isOpen is true (line 69). When isOpen changes to false, React will automatically unmount the portal, but there's no cleanup in case the component unmounts while the portal is open. The useEffect cleanup on lines 49-55 attempts this, but has a dependency issue (see separate comment).

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +56
onClick={() => !isDeleted && handleResponseCommentButton()}
disabled={isClosed || isDeleted}
style={{ lineHeight: '20px', ...(isDeleted ? { opacity: 0.3, cursor: 'not-allowed' } : {}) }}
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

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

The onClick handler on line 54 checks !isDeleted before calling the function, but the button is also disabled when isDeleted is true (line 55). This creates redundant protection. Either the disabled state is sufficient, or if you need the onClick check for some reason, document why both are necessary.

Copilot uses AI. Check for mistakes.
*/
export function checkPermissions(content, action) {
if (content.editableFields.includes(action)) {
if (content.editableFields && content.editableFields.includes(action)) {
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

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

Defensive check added for editableFields is good, but this suggests content.editableFields might be undefined or null. Consider whether this is expected behavior or if the API response should be normalized to always include editableFields as an empty array when not present.

Copilot uses AI. Check for mistakes.
{ 'bg-light-200': isDeleted }, // Gray background for deleted threads
)
}
style={isDeleted ? { opacity: 0.7 } : {}} // Slightly faded for deleted threads
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

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

The inline style opacity: 0.7 uses a magic number. Consider defining this as a CSS class or CSS variable for consistency and maintainability.

Copilot uses AI. Check for mistakes.
* @returns {Promise<{}>}
*/
export const restoreThread = async (threadId, courseId) => {
const url = `${getConfig().LMS_BASE_URL}/api/discussion/v1/restore_content`;
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

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

The API function restoreThread uses a hardcoded endpoint '/api/discussion/v1/restore_content'. This endpoint pattern is inconsistent with other endpoints in the file which use helper functions like getThreadsApiUrl(). Consider creating a helper function for consistency.

Copilot uses AI. Check for mistakes.
}

if (userHasModerationPrivileges || userIsGroupTa) {
// Add reported filter only for group TA and moderators
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

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

The comment says 'Add reported filter only for group TA and moderators' but the condition checks for both userHasModerationPrivileges OR userIsGroupTa. This means moderators AND group TAs will see this filter, not just group TAs and moderators as a combined role. The comment should be clarified to match the actual logic.

Copilot uses AI. Check for mistakes.
contentType,
} = {}) {
const params = snakeCaseObject({
authorId: author, // The backend expects author_id
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

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

The API parameter name has been changed from 'authorId' (with snake_case conversion to 'author_id') on line 153 to use 'authorId' which will be converted to 'author_id' by snakeCaseObject. However, the comment on line 153 says 'The backend expects author_id'. Verify that the backend actually accepts this parameter name, as the getUserPosts function uses 'username' (line 81) for the author parameter.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +54
useEffect(() => () => {
if (isOpen) {
close();
setTarget(null);
setActiveSubmenu(null);
}
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

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

The cleanup effect on lines 49-55 will run on every render because it doesn't have a dependency array. This means it sets up a new cleanup function on every render, which is inefficient. Add an empty dependency array [] to ensure this cleanup only runs on unmount.

Suggested change
useEffect(() => () => {
if (isOpen) {
close();
setTarget(null);
setActiveSubmenu(null);
}
useEffect(() => {
return () => {
if (isOpen) {
close();
setTarget(null);
setActiveSubmenu(null);
}
};

Copilot uses AI. Check for mistakes.
* @returns {Promise<{}>}
*/
export const restoreComment = async (commentId, courseId) => {
const url = `${getConfig().LMS_BASE_URL}/api/discussion/v1/restore_content`;
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

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

The API function restoreComment uses a hardcoded endpoint '/api/discussion/v1/restore_content'. This is the same endpoint as used in threads (src/discussions/posts/data/api.js line 227). Consider extracting this to a shared constant or helper function to avoid duplication and ensure consistency.

Copilot uses AI. Check for mistakes.
<span className={classNames(
'font-weight-500 text-primary-500 font-style align-bottom mr-1',
{ 'font-weight-bolder': !read },
{ 'text-decoration-line-through': isDeleted }, // Line-through for deleted threads
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

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

The inline comment on line 138 says 'Line-through for deleted threads' but this style is applied to all deleted content (threads, responses, and comments based on the displayTitle logic). The comment should be updated to reflect this broader scope or be more generic.

Copilot uses AI. Check for mistakes.
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.

3 participants