feat(fuselage): Render message mentions as plain text instead of pills

Merged
IV
2mo ago
+112-79

Proposed changes (including videos or screenshots)

Mentions are rendered inconsistently between the Web and Mobile clients: Web draws them as a coloured pill, Mobile renders them as ordinary text in a distinct colour. This aligns Web with the Mobile approach — the same treatment WhatsApp uses — so a mention reads as part of the sentence instead of as a badge dropped into it.

MessageHighlight now renders regular text: no background, no border radius, no horizontal padding. Each variant keeps its meaning through colour and a medium (p2m) weight.

VariantUsed forColour
criticalmentions of you, highlighted wordsstatus-font-on-danger (red)
relevant@all / @herestatus-font-on-service-1 (orange)
linkrooms / channelsstatus-font-on-info (blue)
othermentions of other usersstatus-font-on-warning (amber)

All four resolve through semantic tokens, so light, dark and high-contrast themes each get their own shade instead of a single hardcoded value.

Before

before

After

after

Both images are the committed visual-regression baselines, so they are exactly what CI renders. The wording differs between them because the story itself was reworked in this PR — it used to repeat the same critical mention five times and never showed @here.

With and without @ # symbols

A new With Mentions story writes the same sentence twice, with the mentions rendered without and with their @/# symbols — the two states the mention symbol setting toggles between:

with mentions

Also in here

  • Spacing around a mention is now font-derived. Two things were adding fixed space to something that should just be text. display: inline-block contributed the element's border to the line box, so a line containing a mention was ~1.5px taller than a line without one; it is display: inline now, and the Default story's body measures exactly 4 × 20px of line-height. And templates.focus-state reserved a 1px transparent border to keep the focus ring from shifting layout, which on an inline run put a fixed 1px on each side of every mention — the ring is drawn with an outline instead, which paints outside the box without taking up space. Each highlight box now matches its text width exactly, focused or not.
  • The link variant gets its own theme variable. It declared the theme key message-highlight-colors-other-color — the same one the other variant uses — so setting that custom property recoloured both at once. Harmless while both variants shared the same pill, but they are now visually distinct and need to be themable independently. It reads message-highlight-colors-link-color from this PR on.
  • The Message stories gain the With Mentions story above, and the Default story now shows one mention per variant instead of repeating the same one five times. It also stops labelling the channel mention as other, adds the missing @here, renames highlighted text to highlighted word to match the feature name, and restores the spaces the pill padding used to fake.

Removed theme variables: message-highlight-colors-background-* and message-highlight-border-radius are gone, since nothing paints a background anymore. Nothing in the Rocket.Chat codebase reads them.

Issue(s)

DSN-186

Further comments

Why colour + weight rather than a lighter pill. A pill competes with the message text for attention and forces vertical padding into a line box that is only 20px tall, which is what made mentions push their line taller. Colour plus medium weight carries the same "this is a mention" signal at text weight, and it is what Mobile already does, so the two clients stop diverging.

Accessibility. Removing the pill means the mention colour is now read against the room surface rather than against a solid badge, so I checked every combination. All pass WCAG AA for body text in both themes; the tightest is 5.34:1 (other on light).

VariantLightDark
critical8.35:15.90:1
relevant6.47:15.82:1
link6.17:15.77:1
other5.34:17.04:1

Those figures are the palette values that actually apply at runtime. Worth flagging separately: colors.status-font(on-warning) falls back to y800 (#AC892F, 3.29:1 on white) when the palette custom properties are absent, because of the pre-existing TODO(design) in styles/colors.scss where the SCSS fallback and the token disagree (y800 vs y900). That fallback only shows up for a consumer loading fuselage.css without the palette, and it is not introduced here, but other is the first mention variant to depend on that particular token.

Visual regression. Ran the full suite in the CI Playwright container without --update-snapshots first, then regenerated only the affected baselines with --grep: the Default one, plus a new baseline for the With Mentions story. No other story produced a pixel diff. A handful of stories fail locally by timing out while loading, never by pixel difference, and the failing set changes between runs — local flakiness rather than regression. Layout/PositionAnimated Left Start did diff once by ~1px on an animated element and passed on other runs, so I left its baseline alone.

Follow-up outside this repo. Highlighted words are not rendered by Fuselage: the web client wraps them in <mark class="highlight-text">, styled in apps/meteor/app/theme/client/imports/general/base_old.css, and they are still a white-on-red pill. They need the same treatment in Rocket.Chat for the change to be consistent end to end.