Skip to content

feat(messages): highlight reactions you've made - #2709

Merged
dlesl merged 3 commits into
mainfrom
davidleslie/own-reaction-highlight
Nov 6, 2026
Merged

dlesl merged 3 commits into
mainfrom
davidleslie/own-reaction-highlight

Conversation

@dlesl

@dlesl dlesl commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Reactions you've made now stand out from everyone else's. A reaction pill that includes you keeps an accent tint and a thin accent ring after the pointer leaves, so you can see at a glance which emoji you've already added.

How it looks

  • Received messages and the panel: accent step 3 fill with a 1px inset ring in the accent's link ink, which clears 3:1 against the tint and the surface around it in every accent family. The contrast guard now audits that ring.
  • Inside your own bubble at Buzzin': the bubble is already the solid accent, so an accent ring would disappear. There the pill takes the accent's strong overlay (the same one mentions use in your bubble) ringed in the bubble's ink.
  • Under emoji-only and table-only messages, and in compact density: there's no own surface, so the pill uses the panel version, whoever wrote the message.

Changes

  • MessageReactions marks the pill with data-mine, reusing the mine flag it already computes for aria-pressed.
  • Messages.module.css styles that marker through the same per-bubble --reaction-* overrides the existing reaction colors use.
  • New roles affordance-reaction-self and border-reaction-self, plus -own variants for inside your bubble. They're defined in tokens.css, registered in the token registry and the --border-color-* theme namespace, and paired in the contrast guard.
  • DESIGN.md and buzz-help.md describe the new state.

Reactions used to have a pressed highlight, which was dropped in the message-bubble redesign (#773). Design should confirm bringing a highlight back in this form.

Screenshots

Fixture message with a teammate's reactions, where you've also reacted 👍 and ✅.

Before After
Default, light default-light-before default-light-after
Default, dark default-dark-before default-dark-after
Buzzin', light (own bubble) buzzin-light-before buzzin-light-after
Compact, light compact-light-before compact-light-after
Compact, dark compact-dark-before compact-dark-after

🤖 Generated with Claude Code

A reaction pill you're among the reactors of now keeps an accent tint and
a 1px inset accent ring, so you can tell your reactions apart at a glance.
Inside your own bubble at Buzzin', where an accent ring would vanish on the
accent fill, it takes the accent's strong overlay ringed in the bubble's
ink. Compact density has no own surface, so it uses the panel pairing.

Adds affordance-reaction-self and border-reaction-self roles (with -own
variants), registers them, and adds their contrast pairs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: David Leslie <[email protected]>
@dlesl

dlesl commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T00:30:43.338632Z 1c00ccc Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1c00ccc6b7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Accent step 9 fell below the 3:1 non-text target on light Citrus and Bee
(about 1.4:1 on the tint) and on dark Iris bubbles, so the ring that marks
a reaction as yours could be lost. The accent link ink keeps the accent hue
and clears 3:1 everywhere the ring sits.

The contrast guard now audits the ring against the tint inside it and the
grounds around it, per accent family and level, so a faint ring fails.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: David Leslie <[email protected]>
@dlesl
dlesl marked this pull request as ready for review November 6, 2026 00:43
@dlesl
dlesl requested a review from a team as a code owner November 6, 2026 00:43

@kazemek21 kazemek21 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kyle reviewed the PR and the Nexus review-tracker notes. Approved with the non-blocking accessibility suggestion below.

Posted by Codex (gpt-6.1-sol) on behalf of kylez

Forced colors drop the inset ring's box-shadow and replace the tint, which
removed both cues that a reaction is yours. A 1px Highlight outline, inset
like the ring, keeps it visible for high-contrast users.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: David Leslie <[email protected]>
@dlesl
dlesl merged commit 8bd102f into main Nov 6, 2026
58 checks passed
@dlesl
dlesl deleted the davidleslie/own-reaction-highlight branch November 6, 2026 04:29
tlongwell-block added a commit that referenced this pull request Nov 6, 2026
* eva/huddle-audio-fixes: (29 commits)
  fix(huddle): keep calls flowing under stalled sends and multiple devices
  fix(channels): wrap unread conversation stepping around the sidebar (#2682)
  fix(navigation): let bare buzz:// open Buzz without a broken-link notice (#2718)
  fix(inbox): open DM thread rows as the thread (#2587)
  test(channels): complete the context navigation fixture (#2783)
  fix(channels): open a thread's channel at its root message from the #channel link (#2377)
  fix(people): find people by a pasted npub or hex key (#2396)
  test(bestie): open the narrow long-history thread with clickRow (#2723)
  Account-first onboarding and sign-in recovery (#2579)
  fix(agents): scroll the avatar dock with a mouse wheel (#2567)
  fix(avatars): keep Done in view when the avatar popup is short (#2706)
  feat(messages): highlight reactions you've made (#2709)
  feat(usage): move Usage from the sidebar into Settings (#2742)
  fix(messages): keep a playing row mounted in the channel timeline (#2695)
  Fix plugin status declaration closure for author consumers (#2643)
  fix(navigation): keep empty new chats out of Back history (#1617)
  [buzz-app] Recover remote agent message outcomes (#2542)
  feat(messages): name background send destinations and attach the files that fit (#2406)
  feat(drafts): clear a draft from its row (#2725)
  Authorize external application identities in Buzz (#2685)
  ...

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>

# Conflicts:
#	docs/huddles.md
#	src-tauri/src/relay/huddle/audio.rs
wpfleger96 added a commit that referenced this pull request Nov 6, 2026
…leanup

* origin/main: (84 commits)
  Restore macOS Cmd-W window closing when no tab handles it (#2443)
  Make agent transcripts quieter and link finished replies to activity (#2424)
  fix(channels): wrap unread conversation stepping around the sidebar (#2682)
  fix(navigation): let bare buzz:// open Buzz without a broken-link notice (#2718)
  fix(inbox): open DM thread rows as the thread (#2587)
  test(channels): complete the context navigation fixture (#2783)
  fix(channels): open a thread's channel at its root message from the #channel link (#2377)
  fix(people): find people by a pasted npub or hex key (#2396)
  test(bestie): open the narrow long-history thread with clickRow (#2723)
  Account-first onboarding and sign-in recovery (#2579)
  fix(agents): scroll the avatar dock with a mouse wheel (#2567)
  fix(avatars): keep Done in view when the avatar popup is short (#2706)
  feat(messages): highlight reactions you've made (#2709)
  feat(usage): move Usage from the sidebar into Settings (#2742)
  fix(messages): keep a playing row mounted in the channel timeline (#2695)
  Fix plugin status declaration closure for author consumers (#2643)
  fix(navigation): keep empty new chats out of Back history (#1617)
  [buzz-app] Recover remote agent message outcomes (#2542)
  feat(messages): name background send destinations and attach the files that fit (#2406)
  feat(drafts): clear a draft from its row (#2725)
  ...

# Conflicts:
#	src/bundled/builderlab/adapter-type/Activity.test.tsx
kalvinnchau added a commit that referenced this pull request Nov 6, 2026
)

## Problem

Three costs add up while a busy channel is open and new messages arrive.

- **Reading observation restarts on every message.** `useReading` lists
`latestMessageId` as an effect dependency, so each new latest message
tears down the effect and runs it again. That disposes the reading
lease, the observers and the listeners, then measures rows synchronously
in the passive-effect phase (`getClientRects` /
`getBoundingClientRect`). The result is a forced layout per message, and
a dwell that new messages arriving faster than 300 ms never let
complete.
- **Activity reactions feed pruning.** #2638 added this feed. On every
channel-list notification, which live messages trigger, it prunes up to
100 items with `allowed(item.channelId)`. Each call rescans the list and
runs `members.includes(viewer)`, even though the callback has just built
the same allowed set.
- **Own-reaction ring.** #2709 draws the ring with an inset
`box-shadow`, which adds paint-bound long-animation-frame blocking while
the pointer rests on a message.

## Change

This PR combines three changes:

- `useReading` reads the latest message id through `useEffectEvent` at
measurement time, and it is no longer an effect dependency. A new latest
message already mutates the list, and the existing mutation observer
remeasures it in the next frame.
- The reactions prune filter uses `next.has(item.channelId)`, the set
built a few lines above from the same list snapshot and predicate.
- The own-reaction ring is drawn with `outline: 1px solid` and
`outline-offset: -1px` instead of the inset shadow. It uses the same
color variables, and in forced-colors mode only `outline-color:
Highlight` is set.

## No behavior changes

- **Reading.**
- The latest id is read when the bottom row is measured, so catch-up
still targets the newest rendered row. `catchUp` itself still skips when
a newer row exists and revalidates its target.
- If the latest id changes with no DOM mutation, the bottom check finds
no matching shown row, the same outcome as the old restart.
- `rootId`, `channelId`, `session` and `enabled` still restart the
effect.
- A new test checks that a new latest id neither restarts observation
nor takes a lease, and that catch-up reaches the new row after it is
measured. The test fails without this change.
- **Reactions.** `next` holds exactly the ids `allowed` would accept,
built from the same snapshot with no `await` before the filter. `load`
and `eventVisibility` still call `allowed`.
- **Ring.** It has the same width, color and position, inside the pill's
border box, and follows the capsule radius. An outline paints above the
pill's content and an inset shadow below it. The pill's button is
transparent at rest and the hover glyph is transformed, so the ring
looks the same. Outlines follow `border-radius` from Safari 16.4 /
WebKitGTK 2.40.

## Evidence

Main `8c0cb0c1e` and this PR were interleaved for 6 rounds on 4 Linux
and 5 macOS workstations. Each workstation's median is taken first, and
the table shows the mean of those medians.

- **Browser rows.** Production build, Chromium at 4× CPU throttling.
Traffic appends a message every 200 ms to the open channel and to
another one.
- **pointer on message:** the pointer rests on a message, which reveals
its action bar, for 4.5 s.
  - **no pointer:** the same run with the pointer away.
  - **at rest:** the channel stays open for 3 s with no interaction.
- **Unit row.** Vitest micro-benchmark with 400 channels, one of them
with 5,001 members and the viewer at the end of its list, and 100
reaction items in that channel. It delivers 50 channel-list
notifications. It ran on 2 Linux and 2 macOS workstations.

| metric | Linux: main | Linux: this PR | Linux Δ % | macOS: main |
macOS: this PR | macOS Δ % |
|---|---|---|---|---|---|---|
| pointer on message: blocking | 156.2 ms | 35.3 ms | −77.4% | 32.3 ms |
0.0 ms | −100% |
| pointer on message: task | 2,017.7 ms | 1,997.4 ms | −1.0% | 1,458.1
ms | 1,461.7 ms | +0.2% (noise) |
| pointer on message: script | 1,028.8 ms | 1,010.5 ms | −1.8% | 644.5
ms | 640.2 ms | −0.7% (noise) |
| no pointer: blocking | 133.9 ms | 13.7 ms | −89.8% | 25.6 ms | 0.0 ms
| −100% |
| no pointer: task | 1,843.9 ms | 1,864.5 ms | +1.1% (noise) | 1,365.2
ms | 1,353.7 ms | −0.8% |
| at rest: task | 318.0 ms | 324.4 ms | +2.0% (noise, one box +11%) |
235.1 ms | 236.0 ms | +0.4% (noise) |
| reactions list callback ×50 (unit) | 464.4 ms | 6.9 ms | −98.5% |
309.0 ms | 4.0 ms | −98.7% |

Lower is better. Blocking is long-animation-frame blocking time. Every
workstation moved the same way on both blocking rows (per box: Linux
−77.0% to −77.9% and −88.1% to −91.9%; macOS −100% on all five). Neither
variant blocks in the at-rest run.

Checks:
- The `messages` vitest suites pass (96 files, 2,835 tests).
- `inbox-reactions` passes (10 tests).
- `tsc`, `biome` and `check-contrast` pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
baxen added a commit that referenced this pull request Nov 7, 2026
…ring (#2949)

Follow-up to #2709, per design review feedback. Reactions you've made
drop the 1px ring, and their fill swaps between the two bubble colors so
they never sit as a highlight on a highlight.

- **On someone else's message (and on the panel):** your reaction takes
your own bubble's fill and ink (`surface-message-own` /
`text-message-own`).
- **On your own message:** it takes the received bubble's fill and ink
(`surface-message` / `text-standard`).
- New `text-reaction-self` / `text-reaction-self-own` roles pair the
count's ink with each fill. `affordance-reaction-self` / `-own` now
point at the bubble fills, and the Buzzin' strong-overlay override is
gone.
- Removes the `border-reaction-self` / `border-reaction-self-own`
tokens, their utilities, registry entries and contrast checks.
- Forced-colors mode keeps a `Highlight` ring, since it replaces the
fills and the ring is then the only visual mark.
- Updates DESIGN.md and buzz-help.md.

Checks: `pnpm design:check` (contrast pairs updated), `pnpm
design:test`, `MessageReactions.test.tsx`, biome. Screenshots taken in
Chromium from `tests/fixtures/emoji.html?reactions&wrap` (with a local,
uncommitted tweak that makes the message someone else's), light/dark ×
Chillin'/Buzzin'.

---------

Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants