diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md
index 966a71d..0f0d016 100644
--- a/frontend/AGENTS.md
+++ b/frontend/AGENTS.md
@@ -423,9 +423,9 @@ PYTHONPATH=. uv run pytest tests/ -v
## Errata & Known Non-Issues
-### Contacts rollup uses mention styling for unread DMs
+### Contacts use mention styling for unread DMs
-This is intentional. In the sidebar section headers, unread direct messages are treated as mention-equivalent, so the Contacts rollup uses the highlighted mention-style badge for any unread DM. Row-level mention detection remains separate; this note is only about the section summary styling.
+This is intentional. In the sidebar, unread direct messages for actual contact conversations are treated as mention-equivalent for badge styling. That means both the Contacts section header and contact unread badges themselves use the highlighted mention-style colors for unread DMs, including when those contacts appear in Favorites. Repeaters do not inherit this rule, and channel badges still use mention styling only for real `@[name]` mentions.
### RawPacketList always scrolls to bottom
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index 3d5bb2e..f5d2309 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -517,57 +517,65 @@ export function Sidebar({
contact,
});
- const renderConversationRow = (row: ConversationRow) => (
-
0 && '[&_.name]:font-semibold [&_.name]:text-foreground'
- )}
- role="button"
- tabIndex={0}
- aria-current={isActive(row.type, row.id) ? 'page' : undefined}
- onKeyDown={handleKeyboardActivate}
- onClick={() =>
- handleSelectConversation({
- type: row.type,
- id: row.id,
- name: row.name,
- })
- }
- >
- {row.type === 'contact' && row.contact && (
-
- )}
-
{row.name}
-
- {row.notificationsEnabled && (
-
-
-
+ const renderConversationRow = (row: ConversationRow) => {
+ const highlightUnread =
+ row.isMention ||
+ (row.type === 'contact' &&
+ row.contact?.type !== CONTACT_TYPE_REPEATER &&
+ row.unreadCount > 0);
+
+ return (
+ 0 && '[&_.name]:font-semibold [&_.name]:text-foreground'
)}
- {row.unreadCount > 0 && (
-
- {row.unreadCount}
-
+ role="button"
+ tabIndex={0}
+ aria-current={isActive(row.type, row.id) ? 'page' : undefined}
+ onKeyDown={handleKeyboardActivate}
+ onClick={() =>
+ handleSelectConversation({
+ type: row.type,
+ id: row.id,
+ name: row.name,
+ })
+ }
+ >
+ {row.type === 'contact' && row.contact && (
+
)}
-
-
- );
+ {row.name}
+
+ {row.notificationsEnabled && (
+
+
+
+ )}
+ {row.unreadCount > 0 && (
+
+ {row.unreadCount}
+
+ )}
+
+
+ );
+ };
const renderSidebarActionRow = ({
key,
diff --git a/frontend/src/test/sidebar.test.tsx b/frontend/src/test/sidebar.test.tsx
index e70f63b..dde9e63 100644
--- a/frontend/src/test/sidebar.test.tsx
+++ b/frontend/src/test/sidebar.test.tsx
@@ -129,7 +129,7 @@ describe('Sidebar section summaries', () => {
);
});
- it('keeps contact row badges normal while the contacts rollup is always red', () => {
+ it('turns contact row badges red while the contacts rollup remains red', () => {
const { aliceName } = renderSidebar();
expect(within(getSectionHeaderContainer('Contacts')).getByText('3')).toHaveClass(
@@ -140,6 +140,30 @@ describe('Sidebar section summaries', () => {
const aliceRow = screen.getByText(aliceName).closest('div');
if (!aliceRow) throw new Error('Missing Alice row');
expect(within(aliceRow).getByText('3')).toHaveClass(
+ 'bg-badge-mention',
+ 'text-badge-mention-foreground'
+ );
+ });
+
+ it('turns favorite contact row badges red', () => {
+ const { aliceName } = renderSidebar({
+ favorites: [{ type: 'contact', id: '11'.repeat(32) }],
+ });
+
+ const aliceRow = screen.getByText(aliceName).closest('div');
+ if (!aliceRow) throw new Error('Missing Alice row');
+ expect(within(aliceRow).getByText('3')).toHaveClass(
+ 'bg-badge-mention',
+ 'text-badge-mention-foreground'
+ );
+ });
+
+ it('keeps repeater row badges neutral', () => {
+ renderSidebar();
+
+ const relayRow = screen.getByText('Relay').closest('div');
+ if (!relayRow) throw new Error('Missing Relay row');
+ expect(within(relayRow).getByText('4')).toHaveClass(
'bg-badge-unread/90',
'text-badge-unread-foreground'
);