Files
buzz/desktop/tailwind.config.js
tulsi 0e69b3fd7c show mention counts in channel notifications (#6696)
**Category:** fix
**User Impact:** Stream and forum channels now show an accessible
numeric badge for unread mentions while mention chips remain clear in
every theme.

**Problem:** Mention notifications contributed to the app and Dock
badge, but inactive stream and forum rows only became bold, making it
difficult to see where multiple mentions were waiting. Mention styling
and generic destructive colors could also lose contrast or visual
meaning in some themes.

**Solution:** Use the same app-badge projection for non-DM channel
mention counts, while preserving regular unread bolding, thread activity
dots, DM counts, and manual unread behavior. Dedicated notification and
opaque mention-highlight tokens keep the new treatments stable and
readable across syntax themes.

<details>
<summary>File changes</summary>

**desktop/src/features/channels/useUnreadChannels.ts**
Projects app-badge-eligible mention and broadcast counts into stream and
forum channel rows while retaining DM-specific counting and
manual-unread semantics.

**desktop/src/features/sidebar/ui/SidebarSection.tsx**
Renders an accessible numeric notification pill on inactive non-DM
channels and preserves the thread activity dot fallback.

**desktop/src/shared/styles/globals/markdown.css**
Applies the shared opaque yellow highlight to human and agent mention
chips, including hover treatment.

**desktop/src/shared/styles/globals/theme.css**
Adds fixed notification and mention-highlight tokens with
theme-independent contrast.

**desktop/tailwind.config.js**
Exposes the notification token pair through semantic Tailwind utilities.

**desktop/tests/e2e/badge.spec.ts**
Covers aggregated mention counts, broadcasts, unchanged unread tiers,
exact accessible text, and badge contrast under an adversarial theme.

**desktop/tests/e2e/mentions.spec.ts**
Covers human and agent mention styling, hover behavior, dark mode, and
WCAG text contrast.

</details>

## Reproduction steps

1. Open a stream or forum channel, then navigate to another channel.
2. Receive two messages that mention you in the inactive channel.
3. Confirm the inactive row is bold and shows a red `2` pill matching
the two notifications added to the app or Dock badge.
4. Receive a regular channel message and confirm the row only becomes
bold, without a numeric pill.
5. Receive a reply in an interested thread and confirm the channel
retains its activity dot instead of a mention count.
6. Switch between light and dark themes and confirm human and agent
mention chips remain yellow with near-black readable text, including on
hover.

## Screenshots

Screenshots are posted in the PR discussion using immutable
repository-hosted image URLs.

Signed-off-by: tulsi <tulsi@block.xyz>
2026-08-24 11:48:48 -04:00

146 lines
5.8 KiB
JavaScript

/** @type {import('tailwindcss').Config} */
export default {
theme: {
extend: {
// Sub-`text-xs` ramp for meta text (timestamps, count badges, tracking
// labels) and tiny glyphs. These follow the virtual typography rem
// (`--buzz-type-rem` in styles/globals/typography.css), which is
// rem-relative: Cmd +/- zooms it with the rest of the layout, and the
// Font size preference nudges it alone. Do NOT reintroduce arbitrary `text-[…rem]` / `text-[…px]` literals;
// the px-text guard rejects them. Stock scale picks up from xs.
fontSize: {
"2xs": "calc(var(--buzz-type-rem) * 0.6875)", // 11px at 16px type rem
"3xs": "calc(var(--buzz-type-rem) * 0.5)", // 8px at 16px type rem
badge: "calc(var(--buzz-type-rem) * 0.625)", // 10px at 16px type rem
// Shared channel, DM, thread, and composer type. Variables keep app-wide
// font size and keyboard zoom consistent without branching components.
message: [
"var(--conversation-message-font-size)",
{ lineHeight: "var(--conversation-message-line-height)" },
],
"message-timestamp": [
"var(--conversation-timestamp-font-size)",
{ lineHeight: "var(--conversation-timestamp-line-height)" },
],
// 40px at the 16px type rem — onboarding page titles.
title: [
"calc(var(--buzz-type-rem) * 2.5)",
{ lineHeight: "1.15", letterSpacing: "-0.02em" },
],
// 36px at the 16px type rem — backup-step private key.
"nsec-key": [
"calc(var(--buzz-type-rem) * 2.25)",
{ lineHeight: "1.3" },
],
},
lineHeight: {
// Keep fixed Tailwind line-height utilities in the typography scale so
// Cmd +/- cannot enlarge glyphs inside an unchanged line box. Single-
// line surfaces keep their existing truncate/overflow behavior.
3: "calc(var(--buzz-type-rem) * 0.75)",
4: "var(--buzz-type-rem)",
5: "calc(var(--buzz-type-rem) * 1.25)",
6: "calc(var(--buzz-type-rem) * 1.5)",
7: "calc(var(--buzz-type-rem) * 1.75)",
8: "calc(var(--buzz-type-rem) * 2)",
"message-author": "var(--conversation-author-line-height)",
},
boxShadow: {
"content-edge": "-1px -1px 0 0 hsl(var(--sidebar-border) / 0.45)",
// Edge + elevation for a surface anchored to the right of the content
// area, whose only exposed edge faces left. Tailwind's stock shadows are
// all y-offset, so they cast almost nothing sideways — `shadow-xl` on a
// left-facing edge is nearly invisible. Both layers run -x so they wrap
// the surface's rounded left corners: the hairline draws the boundary
// (and carries dark mode, where a black shadow reads as nothing), the
// soft layer carries the lift. A left-only `border` can't do this job —
// it tapers out at each corner instead of turning it.
"panel-left":
"-1px 0 0 0 hsl(var(--border) / 0.8), -16px 0 32px -12px rgb(0 0 0 / 0.18)",
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
spacing: {
4.5: "1.125rem",
"conversation-body": "var(--conversation-body-gap)",
"conversation-list": "var(--conversation-list-item-gap)",
"conversation-paragraph": "var(--conversation-paragraph-gap)",
"conversation-row": "var(--conversation-row-padding-block)",
},
fontFamily: {
sans: [
'"Inter Variable"',
"Inter",
'"Avenir Next"',
'"Segoe UI"',
"sans-serif",
],
},
colors: {
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
notification: {
DEFAULT: "hsl(var(--notification))",
foreground: "hsl(var(--notification-foreground))",
},
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
sidebar: {
DEFAULT: "hsl(var(--sidebar-background))",
foreground: "hsl(var(--sidebar-foreground))",
primary: "hsl(var(--sidebar-primary))",
"primary-foreground": "hsl(var(--sidebar-primary-foreground))",
active: "hsl(var(--sidebar-active))",
"active-foreground": "hsl(var(--sidebar-active-foreground))",
accent: "hsl(var(--sidebar-accent))",
"accent-foreground": "hsl(var(--sidebar-accent-foreground))",
border: "hsl(var(--sidebar-border))",
ring: "hsl(var(--sidebar-ring))",
},
status: {
added: "var(--status-added)",
deleted: "var(--status-deleted)",
modified: "var(--status-modified)",
},
warning: {
DEFAULT: "var(--ui-warning)",
bg: "var(--ui-warning-bg)",
},
},
},
},
plugins: [],
};