diff --git a/docs/buzz-help.md b/docs/buzz-help.md index 04df542872..c62c6972dc 100644 --- a/docs/buzz-help.md +++ b/docs/buzz-help.md @@ -402,7 +402,7 @@ revokes all of them, and a pairing unused for 30 days expires. kind, and **From** by sender (Anyone, People or Agents). Turn on **Unread** to see only what you haven't read: there, a row's ✓ marks it as read and it leaves the list, with **Undo** for a moment; when nothing is left it says **All caught up**. If loading fails, use **Retry activity** on the page or preview to retry; recovered - activity clears the loading error. + activity clears the loading error. Pink dots identify unread conversations in Activity and unread activity in channel and DM sidebar rows. A DM row whose unread messages are all in one thread opens that thread; later DM messages outside it stay unread until you open the whole DM, which its row then does. **Reactions** shows recent reactions to your messages, with shared emoji grouped together per message and each person’s remaining emoji in their own row; selecting it turns Unread off. diff --git a/scripts/design-system/check-contrast.mjs b/scripts/design-system/check-contrast.mjs index 2b02a92126..abf016b39e 100644 --- a/scripts/design-system/check-contrast.mjs +++ b/scripts/design-system/check-contrast.mjs @@ -275,6 +275,7 @@ const BOUNDARY_ROLES = [ "--status-avatar-online-border", "--status-away", "--status-offline", + "--status-unread", // Chart marks need 3:1 against the surfaces a chart sits on. "--chart-series-1", "--chart-series-2", @@ -470,7 +471,9 @@ function audit(mode, map, baseline) { } for (const role of BOUNDARY_ROLES) { const surfaces = - role.startsWith("--status-avatar-") || role === "--status-away" + role.startsWith("--status-avatar-") || + role === "--status-away" || + role === "--status-unread" ? [ ...BOUNDARY_SURFACES, "--affordance-selected", diff --git a/src/bundled/channels/Channels.module.css b/src/bundled/channels/Channels.module.css index d1732fe7eb..364b1abcf5 100644 --- a/src/bundled/channels/Channels.module.css +++ b/src/bundled/channels/Channels.module.css @@ -886,7 +886,7 @@ details:not([open]) > summary .sectionChevron { width: 0.375rem; height: 0.375rem; border-radius: 50%; - background: var(--affordance-prominent-mark); + background: var(--status-unread); } .threadActivityDot { cursor: help; diff --git a/src/features/messages/MessageListRow.module.css b/src/features/messages/MessageListRow.module.css index 78edafe2b5..2d6b01f1b2 100644 --- a/src/features/messages/MessageListRow.module.css +++ b/src/features/messages/MessageListRow.module.css @@ -16,7 +16,7 @@ width: var(--space-2); height: var(--space-2); border-radius: var(--radius-pill); - background: var(--text-navigation-unread); + background: var(--status-unread); } .rowDate { diff --git a/src/shared/design-system/styles/tokens.css b/src/shared/design-system/styles/tokens.css index f1f02bb277..c19b2067c5 100644 --- a/src/shared/design-system/styles/tokens.css +++ b/src/shared/design-system/styles/tokens.css @@ -1161,6 +1161,7 @@ --status-avatar-online-border: var(--green-11); --status-away: var(--amber-10); --status-offline: var(--neutral-8); + --status-unread: var(--magenta-9); /* Chart series marks, in this order; validated as a set for colour-vision * separation and 3:1 against the panel. Text never uses them. */ --chart-series-1: var(--blue-10); @@ -1314,6 +1315,7 @@ --status-avatar-online-border: var(--green-11); --status-away: var(--amber-10); --status-offline: var(--neutral-10); + --status-unread: var(--magenta-11); /* Dark steps stay in the band where no series outshines the others. */ --chart-series-1: var(--blue-9); --chart-series-2: var(--orange-8); diff --git a/src/shared/design-system/tokens/registry.ts b/src/shared/design-system/tokens/registry.ts index 4b67c05af7..f5c6eb931c 100644 --- a/src/shared/design-system/tokens/registry.ts +++ b/src/shared/design-system/tokens/registry.ts @@ -813,6 +813,13 @@ export const ROLE_GROUPS: RoleGroup[] = [ use: "Offline presence dot.", status: "core", }, + { + token: "status-unread", + variable: "--status-unread", + pointsAt: "magenta-9 light / magenta-11 dark", + use: "Unread dots in Activity and channel/DM sidebars; independent of the chosen accent. Selected sidebar rows retain their paired selection ink.", + status: "core", + }, ], }, { diff --git a/tests/browser/inbox.spec.mjs b/tests/browser/inbox.spec.mjs index ffc16811e6..9ae5b67dbb 100644 --- a/tests/browser/inbox.spec.mjs +++ b/tests/browser/inbox.spec.mjs @@ -245,6 +245,10 @@ test("Inbox opens the exact thread, shares read state, and fits the workspace", const opener = mentionRow.getByRole("button", { name: /^Open / }); const dot = opener.getByRole("img", { name: "Unread", exact: true }); await expect(dot).toBeVisible(); + await expect(dot.locator("span")).toHaveCSS( + "background-color", + mode === "light" ? "rgb(223, 30, 133)" : "rgb(244, 188, 207)", + ); const dotLayout = await dot.evaluate((element) => { const button = element.closest("button"); const label = button.querySelector(".navigation-item-label"); diff --git a/tests/browser/sidebar-unread.spec.mjs b/tests/browser/sidebar-unread.spec.mjs index 7a28b078ce..b919e76893 100644 --- a/tests/browser/sidebar-unread.spec.mjs +++ b/tests/browser/sidebar-unread.spec.mjs @@ -534,7 +534,7 @@ test("session changes discard the previous sidebar targets and manual unread sti } }); -test("priority dots stay aligned and use the prominent mark color in both modes", async ({ +test("priority dots stay aligned and use pink unread color in both modes", async ({ page, app, }, info) => { @@ -584,9 +584,7 @@ test("priority dots stay aligned and use the prominent mark color in both modes" const primary = await page.evaluate(() => { const root = getComputedStyle(document.documentElement); const probe = document.createElement("span"); - probe.style.backgroundColor = root.getPropertyValue( - "--affordance-prominent-mark", - ); + probe.style.backgroundColor = root.getPropertyValue("--status-unread"); document.body.append(probe); const color = getComputedStyle(probe).backgroundColor; probe.remove(); @@ -594,6 +592,26 @@ test("priority dots stay aligned and use the prominent mark color in both modes" }); await expect(dot).toHaveCSS("background-color", primary); } + // The existing selected-row recipe uses its accent fill's paired ink. + // A pink dot would disappear on the light Magenta selection fill. + await page.evaluate(() => { + document.documentElement.dataset.colorMode = "light"; + document.documentElement.dataset.accent = "magenta"; + }); + const dm = row(page, "dm-030"); + await dm.click(); + await expect(dm).toHaveAttribute("aria-current", "page"); + await dm.click({ button: "right" }); + await page + .getByRole("menuitem", { name: "Mark as read", exact: true }) + .click(); + await expect(page.getByRole("menu")).toHaveCount(0); + await dm.click({ button: "right" }); + await page + .getByRole("menuitem", { name: "Mark as unread", exact: true }) + .click(); + await expect(dot).toBeVisible(); + await expect(dot).toHaveCSS("background-color", "rgb(255, 255, 255)"); }); // One small real-app journey owns preview wiring and CSS geometry. The component