From 7d42bd4de50b2a68b3647a46f92548039d874114 Mon Sep 17 00:00:00 2001 From: Hitanshu Paruthi Date: Tue, 10 Nov 2026 12:51:48 -0500 Subject: [PATCH 1/3] fix(activity): use pink dots for unread conversations Signed-off-by: Hitanshu Paruthi --- docs/buzz-help.md | 2 +- scripts/design-system/check-contrast.mjs | 5 ++++- src/features/messages/MessageListRow.module.css | 2 +- src/shared/design-system/styles/tokens.css | 2 ++ src/shared/design-system/tokens/registry.ts | 7 +++++++ tests/browser/inbox.spec.mjs | 4 ++++ 6 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/buzz-help.md b/docs/buzz-help.md index 04df542872..1eb3e314de 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. 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/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..0df8e4e0ed 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 Activity conversation dot on panel, hover and selected row surfaces; independent of the chosen accent.", + 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"); From 7cd1350f8bcd0c6b3d904cfae8926238a503c1c3 Mon Sep 17 00:00:00 2001 From: Hitanshu Paruthi Date: Tue, 10 Nov 2026 13:06:53 -0500 Subject: [PATCH 2/3] fix(sidebar): match unread dots to activity pink Signed-off-by: Hitanshu Paruthi --- docs/buzz-help.md | 2 +- src/bundled/channels/Channels.module.css | 2 +- src/shared/design-system/tokens/registry.ts | 2 +- tests/browser/sidebar-unread.spec.mjs | 6 ++---- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/buzz-help.md b/docs/buzz-help.md index 1eb3e314de..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. Pink dots identify unread conversations in Activity. + 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/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/shared/design-system/tokens/registry.ts b/src/shared/design-system/tokens/registry.ts index 0df8e4e0ed..f5c6eb931c 100644 --- a/src/shared/design-system/tokens/registry.ts +++ b/src/shared/design-system/tokens/registry.ts @@ -817,7 +817,7 @@ export const ROLE_GROUPS: RoleGroup[] = [ token: "status-unread", variable: "--status-unread", pointsAt: "magenta-9 light / magenta-11 dark", - use: "Unread Activity conversation dot on panel, hover and selected row surfaces; independent of the chosen accent.", + 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/sidebar-unread.spec.mjs b/tests/browser/sidebar-unread.spec.mjs index 7a28b078ce..a889f7d63e 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(); From fac81077ee8c17b04cf071ef6c4bdf56df153b58 Mon Sep 17 00:00:00 2001 From: Hitanshu Paruthi Date: Tue, 10 Nov 2026 13:21:57 -0500 Subject: [PATCH 3/3] test(sidebar): cover selected unread dot contrast Signed-off-by: Hitanshu Paruthi --- tests/browser/sidebar-unread.spec.mjs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/browser/sidebar-unread.spec.mjs b/tests/browser/sidebar-unread.spec.mjs index a889f7d63e..b919e76893 100644 --- a/tests/browser/sidebar-unread.spec.mjs +++ b/tests/browser/sidebar-unread.spec.mjs @@ -592,6 +592,26 @@ test("priority dots stay aligned and use pink unread color in both modes", async }); 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