From 23a14a2fa36e91c1ebaffe83562958f9eff12418 Mon Sep 17 00:00:00 2001
From: Nimrod Kramer <41470823+nimrodkra@users.noreply.github.com>
Date: Tue, 31 Mar 2026 23:15:43 +0300
Subject: [PATCH 1/4] fix: keep below-threshold label on cards only
Remove below-threshold label rendering from post page, repost modal, and extension companion while preserving card surfaces and adding focused non-card regression tests.
Made-with: Cursor
---
.../companion/CompanionEngagements.spec.tsx | 60 ++++++++++++++++++
.../src/companion/CompanionEngagements.tsx | 5 +-
.../components/modals/RepostListItem.spec.tsx | 47 ++++++++++++++
.../src/components/modals/RepostListItem.tsx | 19 +++---
.../post/PostUpvotesCommentsCount.spec.tsx | 61 +++++++++++++++++++
.../post/PostUpvotesCommentsCount.tsx | 18 +++---
6 files changed, 186 insertions(+), 24 deletions(-)
create mode 100644 packages/extension/src/companion/CompanionEngagements.spec.tsx
create mode 100644 packages/shared/src/components/modals/RepostListItem.spec.tsx
create mode 100644 packages/shared/src/components/post/PostUpvotesCommentsCount.spec.tsx
diff --git a/packages/extension/src/companion/CompanionEngagements.spec.tsx b/packages/extension/src/companion/CompanionEngagements.spec.tsx
new file mode 100644
index 0000000000..7c0ab2df51
--- /dev/null
+++ b/packages/extension/src/companion/CompanionEngagements.spec.tsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { useQueryClient } from '@tanstack/react-query';
+import { UserVote } from '@dailydotdev/shared/src/graphql/posts';
+import type { PostBootData } from '@dailydotdev/shared/src/lib/boot';
+import { CompanionEngagements } from './CompanionEngagements';
+
+const mockUseConditionalFeature = jest.fn();
+const mockUseAuthContext = jest.fn();
+const mockUseQueryClient = useQueryClient as jest.Mock;
+
+jest.mock('@dailydotdev/shared/src/hooks/useConditionalFeature', () => ({
+ useConditionalFeature: () => mockUseConditionalFeature(),
+}));
+
+jest.mock('@dailydotdev/shared/src/contexts/AuthContext', () => ({
+ useAuthContext: () => mockUseAuthContext(),
+}));
+
+jest.mock('@dailydotdev/shared/src/hooks/companion', () => ({
+ useRawBackgroundRequest: jest.fn(),
+}));
+
+jest.mock('@tanstack/react-query', () => ({
+ ...jest.requireActual('@tanstack/react-query'),
+ useQueryClient: jest.fn(),
+}));
+
+describe('CompanionEngagements', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockUseAuthContext.mockReturnValue({ user: { id: 'user-id' } });
+ mockUseConditionalFeature.mockReturnValue({
+ value: {
+ threshold: 3,
+ belowThresholdLabel: 'New',
+ newWindowHours: 24,
+ },
+ });
+ mockUseQueryClient.mockReturnValue({
+ setQueryData: jest.fn(),
+ });
+ });
+
+ it('does not render below-threshold label in companion', () => {
+ const post = {
+ id: 'post-id',
+ createdAt: new Date().toISOString(),
+ numUpvotes: 1,
+ numComments: 2,
+ userState: {
+ vote: UserVote.None,
+ },
+ } as PostBootData;
+
+ render();
+
+ expect(screen.queryByText('New')).not.toBeInTheDocument();
+ });
+});
diff --git a/packages/extension/src/companion/CompanionEngagements.tsx b/packages/extension/src/companion/CompanionEngagements.tsx
index ce3b2501a2..b9b6b8a953 100644
--- a/packages/extension/src/companion/CompanionEngagements.tsx
+++ b/packages/extension/src/companion/CompanionEngagements.tsx
@@ -47,7 +47,7 @@ export function CompanionEngagements({
const upvotes = post.numUpvotes ?? 0;
const comments = post.numComments ?? 0;
const userHasUpvoted = post.userState?.vote === UserVote.Up;
- const { showCount, belowThresholdLabel } = getUpvoteCountDisplay(
+ const { showCount } = getUpvoteCountDisplay(
upvotes,
upvoteThresholdConfig.threshold,
upvoteThresholdConfig.belowThresholdLabel,
@@ -68,9 +68,6 @@ export function CompanionEngagements({
{upvotes > 1 ? 's' : ''}
)}
- {!showCount && !!belowThresholdLabel && (
- {belowThresholdLabel}
- )}
{comments > 0 && (
{largeNumberFormat(comments)}
diff --git a/packages/shared/src/components/modals/RepostListItem.spec.tsx b/packages/shared/src/components/modals/RepostListItem.spec.tsx
new file mode 100644
index 0000000000..daf420bac2
--- /dev/null
+++ b/packages/shared/src/components/modals/RepostListItem.spec.tsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import type { Post } from '../../graphql/posts';
+import { UserVote } from '../../graphql/posts';
+import { RepostListItem } from './RepostListItem';
+
+const mockUseConditionalFeature = jest.fn();
+const mockUseAuthContext = jest.fn();
+
+jest.mock('../../hooks/useConditionalFeature', () => ({
+ useConditionalFeature: () => mockUseConditionalFeature(),
+}));
+
+jest.mock('../../contexts/AuthContext', () => ({
+ useAuthContext: () => mockUseAuthContext(),
+}));
+
+describe('RepostListItem', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockUseAuthContext.mockReturnValue({ user: { id: 'user-id' } });
+ mockUseConditionalFeature.mockReturnValue({
+ value: {
+ threshold: 3,
+ belowThresholdLabel: 'New',
+ newWindowHours: 24,
+ },
+ });
+ });
+
+ it('does not render below-threshold label in repost modal items', () => {
+ const post = {
+ id: 'post-id',
+ title: '',
+ createdAt: new Date().toISOString(),
+ numUpvotes: 1,
+ numComments: 2,
+ userState: {
+ vote: UserVote.None,
+ },
+ } as Post;
+
+ render();
+
+ expect(screen.queryByText('New')).not.toBeInTheDocument();
+ });
+});
diff --git a/packages/shared/src/components/modals/RepostListItem.tsx b/packages/shared/src/components/modals/RepostListItem.tsx
index 4a72e92e5a..80ca552ce0 100644
--- a/packages/shared/src/components/modals/RepostListItem.tsx
+++ b/packages/shared/src/components/modals/RepostListItem.tsx
@@ -37,15 +37,14 @@ export function RepostListItem({
const upvotes = post.numUpvotes ?? 0;
const comments = post.numComments ?? 0;
const userHasUpvoted = post.userState?.vote === UserVote.Up;
- const { showCount: showUpvotes, belowThresholdLabel: upvoteLabel } =
- getUpvoteCountDisplay(
- upvotes,
- upvoteThresholdConfig.threshold,
- upvoteThresholdConfig.belowThresholdLabel,
- userHasUpvoted,
- post.createdAt,
- upvoteThresholdConfig.newWindowHours,
- );
+ const { showCount: showUpvotes } = getUpvoteCountDisplay(
+ upvotes,
+ upvoteThresholdConfig.threshold,
+ upvoteThresholdConfig.belowThresholdLabel,
+ userHasUpvoted,
+ post.createdAt,
+ upvoteThresholdConfig.newWindowHours,
+ );
const { author } = post;
const showSquadPreview = !isUserSource && !!source;
const isPrivateSquad = !!source && !source.public;
@@ -144,7 +143,7 @@ export function RepostListItem({
- {showUpvotes ? largeNumberFormat(upvotes) : upvoteLabel}
+ {showUpvotes ? largeNumberFormat(upvotes) : ''}
diff --git a/packages/shared/src/components/post/PostUpvotesCommentsCount.spec.tsx b/packages/shared/src/components/post/PostUpvotesCommentsCount.spec.tsx
new file mode 100644
index 0000000000..e26177ac5c
--- /dev/null
+++ b/packages/shared/src/components/post/PostUpvotesCommentsCount.spec.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import type { Post } from '../../graphql/posts';
+import { UserVote } from '../../graphql/posts';
+import { PostUpvotesCommentsCount } from './PostUpvotesCommentsCount';
+
+const mockUseConditionalFeature = jest.fn();
+const mockUseAuthContext = jest.fn();
+const mockOpenModal = jest.fn();
+
+jest.mock('../../hooks/useConditionalFeature', () => ({
+ useConditionalFeature: () => mockUseConditionalFeature(),
+}));
+
+jest.mock('../../contexts/AuthContext', () => ({
+ useAuthContext: () => mockUseAuthContext(),
+}));
+
+jest.mock('../../hooks/useLazyModal', () => ({
+ useLazyModal: () => ({ openModal: mockOpenModal }),
+}));
+
+jest.mock('../../hooks/useCoresFeature', () => ({
+ useHasAccessToCores: () => false,
+}));
+
+jest.mock('../../lib/user', () => ({
+ canViewPostAnalytics: () => false,
+}));
+
+describe('PostUpvotesCommentsCount', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockUseAuthContext.mockReturnValue({ user: { id: 'user-id' } });
+ mockUseConditionalFeature.mockReturnValue({
+ value: {
+ threshold: 3,
+ belowThresholdLabel: 'New',
+ newWindowHours: 24,
+ },
+ });
+ });
+
+ it('does not render below-threshold label on post page', () => {
+ const post = {
+ id: 'post-id',
+ createdAt: new Date().toISOString(),
+ numUpvotes: 1,
+ numComments: 0,
+ numAwards: 0,
+ numReposts: 0,
+ userState: {
+ vote: UserVote.None,
+ },
+ } as Post;
+
+ render();
+
+ expect(screen.queryByText('New')).not.toBeInTheDocument();
+ });
+});
diff --git a/packages/shared/src/components/post/PostUpvotesCommentsCount.tsx b/packages/shared/src/components/post/PostUpvotesCommentsCount.tsx
index 8e689fd0c9..0a0be12118 100644
--- a/packages/shared/src/components/post/PostUpvotesCommentsCount.tsx
+++ b/packages/shared/src/components/post/PostUpvotesCommentsCount.tsx
@@ -45,15 +45,14 @@ export function PostUpvotesCommentsCount({
const reposts = post.numReposts || 0;
const hasAccessToCores = useHasAccessToCores();
const userHasUpvoted = post.userState?.vote === UserVote.Up;
- const { showCount: showUpvotes, belowThresholdLabel: upvoteLabel } =
- getUpvoteCountDisplay(
- upvotes,
- upvoteThresholdConfig.threshold,
- upvoteThresholdConfig.belowThresholdLabel,
- userHasUpvoted,
- post.createdAt,
- upvoteThresholdConfig.newWindowHours,
- );
+ const { showCount: showUpvotes } = getUpvoteCountDisplay(
+ upvotes,
+ upvoteThresholdConfig.threshold,
+ upvoteThresholdConfig.belowThresholdLabel,
+ userHasUpvoted,
+ post.createdAt,
+ upvoteThresholdConfig.newWindowHours,
+ );
const onRepostsClick = () =>
openModal({
type: LazyModal.RepostsPopup,
@@ -86,7 +85,6 @@ export function PostUpvotesCommentsCount({
{largeNumberFormat(upvotes)} Upvote{upvotes > 1 ? 's' : ''}
)}
- {!showUpvotes && !!upvoteLabel && {upvoteLabel}}
{comments > 0 && (
{largeNumberFormat(comments)}
From 3a6f7996aeb6e14c6ff9be09a1722042e6f7bd81 Mon Sep 17 00:00:00 2001
From: Nimrod Kramer <41470823+nimrodkra@users.noreply.github.com>
Date: Tue, 31 Mar 2026 23:29:41 +0300
Subject: [PATCH 2/4] fix: hide repost upvote row when count is hidden
Remove the orphan upvote icon in repost modal items by rendering the upvote row only when the numeric count is visible, and extend regression coverage.
Made-with: Cursor
---
.../src/components/modals/RepostListItem.spec.tsx | 1 +
.../shared/src/components/modals/RepostListItem.tsx | 13 +++++++++----
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/packages/shared/src/components/modals/RepostListItem.spec.tsx b/packages/shared/src/components/modals/RepostListItem.spec.tsx
index daf420bac2..4ff7b41dda 100644
--- a/packages/shared/src/components/modals/RepostListItem.spec.tsx
+++ b/packages/shared/src/components/modals/RepostListItem.spec.tsx
@@ -43,5 +43,6 @@ describe('RepostListItem', () => {
render();
expect(screen.queryByText('New')).not.toBeInTheDocument();
+ expect(screen.queryByTestId('repost-upvotes')).not.toBeInTheDocument();
});
});
diff --git a/packages/shared/src/components/modals/RepostListItem.tsx b/packages/shared/src/components/modals/RepostListItem.tsx
index 80ca552ce0..76bbc6f4df 100644
--- a/packages/shared/src/components/modals/RepostListItem.tsx
+++ b/packages/shared/src/components/modals/RepostListItem.tsx
@@ -141,10 +141,15 @@ export function RepostListItem({
{/* Upvotes and comments */}
-
-
- {showUpvotes ? largeNumberFormat(upvotes) : ''}
-
+ {showUpvotes && (
+
+
+ {largeNumberFormat(upvotes)}
+
+ )}
{largeNumberFormat(comments)}
From b2cd71e3f8dbcabb3d8753bcae58cfae492a5242 Mon Sep 17 00:00:00 2001
From: Nimrod Kramer <41470823+nimrodkra@users.noreply.github.com>
Date: Tue, 31 Mar 2026 23:40:04 +0300
Subject: [PATCH 3/4] fix: hide metadata label on post pages
Prevent below-threshold upvote labels from appearing in post-page metadata while keeping card metadata behavior unchanged for surfaces that pass explicit upvote counts.
Made-with: Cursor
---
.../cards/common/PostMetadata.spec.tsx | 59 +++++++++++++++++++
.../components/cards/common/PostMetadata.tsx | 12 +++-
.../src/components/post/PostContent.tsx | 1 +
.../post/SocialTwitterPostContent.tsx | 1 +
4 files changed, 70 insertions(+), 3 deletions(-)
create mode 100644 packages/shared/src/components/cards/common/PostMetadata.spec.tsx
diff --git a/packages/shared/src/components/cards/common/PostMetadata.spec.tsx b/packages/shared/src/components/cards/common/PostMetadata.spec.tsx
new file mode 100644
index 0000000000..781a8fa4e2
--- /dev/null
+++ b/packages/shared/src/components/cards/common/PostMetadata.spec.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import PostMetadata from './PostMetadata';
+
+const mockUseConditionalFeature = jest.fn();
+const mockUseAuthContext = jest.fn();
+
+jest.mock('../../../hooks/useConditionalFeature', () => ({
+ useConditionalFeature: () => mockUseConditionalFeature(),
+}));
+
+jest.mock('../../../contexts/AuthContext', () => ({
+ useAuthContext: () => mockUseAuthContext(),
+}));
+
+describe('PostMetadata upvote label visibility', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockUseAuthContext.mockReturnValue({ user: { id: 'user-id' } });
+ mockUseConditionalFeature.mockReturnValue({
+ value: {
+ threshold: 3,
+ belowThresholdLabel: 'New',
+ newWindowHours: 24,
+ },
+ });
+ });
+
+ it('renders below-threshold label by default', () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText('New')).toBeInTheDocument();
+ });
+
+ it('does not render below-threshold label when disabled for non-card surfaces', () => {
+ render(
+ ,
+ );
+
+ expect(screen.queryByText('New')).not.toBeInTheDocument();
+ });
+
+ it('does not render below-threshold label when upvote count is not provided', () => {
+ render();
+
+ expect(screen.queryByText('New')).not.toBeInTheDocument();
+ });
+});
diff --git a/packages/shared/src/components/cards/common/PostMetadata.tsx b/packages/shared/src/components/cards/common/PostMetadata.tsx
index 553e0ddcd0..b5ad42553c 100644
--- a/packages/shared/src/components/cards/common/PostMetadata.tsx
+++ b/packages/shared/src/components/cards/common/PostMetadata.tsx
@@ -25,6 +25,7 @@ interface PostMetadataProps
domain?: ReactNode;
pollMetadata?: PollMetadataProps;
userHasUpvoted?: boolean;
+ showBelowThresholdLabel?: boolean;
}
export default function PostMetadata({
@@ -38,7 +39,9 @@ export default function PostMetadata({
domain,
pollMetadata,
userHasUpvoted = false,
+ showBelowThresholdLabel = true,
}: PostMetadataProps): ReactElement {
+ const hasUpvoteCount = typeof numUpvotes === 'number';
const upvoteCount = numUpvotes ?? 0;
const readTimeValue = readTime ?? 0;
const timeActionContent = isVideoType ? 'watch' : 'read';
@@ -94,7 +97,8 @@ export default function PostMetadata({
),
},
!!showReadTime && domain && { key: 'domain', node: domain },
- showUpvoteCount && {
+ hasUpvoteCount &&
+ showUpvoteCount && {
key: 'upvotes',
node: (
@@ -102,8 +106,10 @@ export default function PostMetadata({
),
},
- !showUpvoteCount &&
- !!upvoteLabel && {
+ hasUpvoteCount &&
+ !showUpvoteCount &&
+ !!upvoteLabel &&
+ showBelowThresholdLabel && {
key: 'upvotes',
node: {upvoteLabel},
},
diff --git a/packages/shared/src/components/post/PostContent.tsx b/packages/shared/src/components/post/PostContent.tsx
index d9bf98a3c5..9eaefbdac9 100644
--- a/packages/shared/src/components/post/PostContent.tsx
+++ b/packages/shared/src/components/post/PostContent.tsx
@@ -198,6 +198,7 @@ export function PostContentRaw({
createdAt={post.createdAt}
readTime={post.readTime}
isVideoType={isVideoType}
+ showBelowThresholdLabel={false}
className={metadataClassName}
domain={
!isVideoType &&
diff --git a/packages/shared/src/components/post/SocialTwitterPostContent.tsx b/packages/shared/src/components/post/SocialTwitterPostContent.tsx
index 270ef32e33..c943ebb8c9 100644
--- a/packages/shared/src/components/post/SocialTwitterPostContent.tsx
+++ b/packages/shared/src/components/post/SocialTwitterPostContent.tsx
@@ -161,6 +161,7 @@ function SocialTwitterPostContentRaw({
{!!post.createdAt && }
From d86bd042d6470bc6fb331b29c3539bdb71e943a3 Mon Sep 17 00:00:00 2001
From: Nimrod Kramer <41470823+nimrodkra@users.noreply.github.com>
Date: Tue, 31 Mar 2026 23:47:35 +0300
Subject: [PATCH 4/4] chore: fix metadata formatting for lint
Apply prettier-consistent indentation in PostMetadata to satisfy shared lint checks.
Made-with: Cursor
---
.../src/components/cards/common/PostMetadata.tsx | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/packages/shared/src/components/cards/common/PostMetadata.tsx b/packages/shared/src/components/cards/common/PostMetadata.tsx
index b5ad42553c..5713783315 100644
--- a/packages/shared/src/components/cards/common/PostMetadata.tsx
+++ b/packages/shared/src/components/cards/common/PostMetadata.tsx
@@ -99,13 +99,13 @@ export default function PostMetadata({
!!showReadTime && domain && { key: 'domain', node: domain },
hasUpvoteCount &&
showUpvoteCount && {
- key: 'upvotes',
- node: (
-
- {largeNumberFormat(upvoteCount)} upvote{upvoteCount > 1 ? 's' : ''}
-
- ),
- },
+ key: 'upvotes',
+ node: (
+
+ {largeNumberFormat(upvoteCount)} upvote{upvoteCount > 1 ? 's' : ''}
+
+ ),
+ },
hasUpvoteCount &&
!showUpvoteCount &&
!!upvoteLabel &&