Skip to content

feat(channels): show reactor's short name next to emoji reactions (#4243) - #4247

Merged
Yeraze merged 1 commit into
mainfrom
feat/4243-reactor-short-name
Jul 21, 2026
Merged

feat(channels): show reactor's short name next to emoji reactions (#4243)#4247
Yeraze merged 1 commit into
mainfrom
feat/4243-reactor-short-name

Conversation

@Yeraze

@Yeraze Yeraze commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Closes #4243

Problem

In Channel Messages, the reacting node's short name was only available through the reaction bubble's title attribute — a browser hover tooltip. That's unreachable on touch devices and invisible at a glance, so reactions read as anonymous.

getNodeShortName(reaction.from) was already being called at that exact call site purely to feed the tooltip.

Change

Render the short name inline beside the emoji (.reaction__author), keeping the tooltip. The label is smaller and lighter than the emoji so the emoji stays the primary mark, and it picks up a contrasting color in .mine vs .theirs bubbles.

getNodeShortName never returns empty — it falls back to the last 4 chars of the node id — so the label always renders; the truthiness guard is defensive only.

Tests

This adds the first test file for ChannelsTab. The component takes all 37 required values as props and uses no useContext directly, but it does call three hooks internally (useNodes, useSettings, useNotificationMuteSettings), so those are mocked rather than standing up the full provider tree. ResizeObserver is stubbed locally rather than in src/test/setup.ts, to avoid changing the environment for every other test in this PR.

I verified the test catches the regression rather than assuming it: with the source change stashed, the two behavior assertions fail and the two unchanged-behavior guards (tooltip preserved, parent not rendered as a bubble) still pass.

WITHOUT FIX -> success: false  passed: 2  failed: 2
  now failing: renders the reactor's short name inline beside the emoji
  now failing: keeps the emoji itself rendered alongside the name

Validation

  • ChannelsTab.reactions.test.tsx — 4 tests, success: true
  • npx tsc --noEmit — no errors
  • npx eslint on both changed source files — clean

Note

Multiple reactors with the same emoji already render as separate bubbles, so each gets its own name label — there's no grouping/count behavior to reconcile. If you'd prefer grouped bubbles (👍 3 with names in the tooltip), that's a different design and worth its own issue.

🤖 Generated with Claude Code

https://claude.ai/code/session_016bxVewVnissUKiGZmhf9FW

)

The reacting node's short name was only reachable via the bubble's
`title` attribute -- a hover tooltip, which is unusable on touch and
invisible at a glance. `getNodeShortName(reaction.from)` was already
being called at this exact call site to feed that tooltip.

Render it inline beside the emoji instead. The tooltip is kept, so this
is additive rather than a replacement.

Adds the first test file for ChannelsTab. It mocks useNodes /
useSettings / useNotificationMuteSettings rather than standing up the
provider tree, and stubs ResizeObserver locally rather than editing the
shared test setup.

Verified the test actually catches the regression: with the source
change reverted, the two behavior assertions fail and the two
unchanged-behavior guards still pass.

Closes #4243

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bxVewVnissUKiGZmhf9FW
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review: feat(channels): show reactor's short name next to emoji reactions (#4243)

Tasks:

  • Gather context and read changed files
  • Analyze code quality and best practices
  • Check for potential bugs or issues
  • Review test coverage
  • Post final review

Summary

This is a clean, well-scoped change. The implementation is minimal and correct, and notably this PR establishes the first test file for ChannelsTab. Here's my detailed review:


ChannelsTab.tsx

ChannelsTab.tsx:1141-1152 — The core change:

const reactorName = getNodeShortName(reaction.from);
return (
  <span
    key={reaction.id}
    className={`reaction ${isMyMessage(reaction) ? 'mine' : 'theirs'}`}
    { name: reactorName })}
    onClick={() => handleSendTapback(reaction.text, msg)}
  >
    {reaction.text}
    {reactorName && (
      <span className="reaction__author">{reactorName}</span>
    )}
  </span>
);

Minor observation: getNodeShortName is documented in the PR and confirmed in the code to never return an empty string (it falls back to the last 4 hex chars of the node ID), so {reactorName && ...} is always truthy. This is noted as "defensive only" in the PR description, which is accurate — no bug, just minor redundancy. Given the PR's own acknowledgement of this, it's fine to keep as-is.

No issues with the implementation itself.


nodes.css

src/styles/nodes.css:1665-1682 — New .reaction__author styles:

.reaction__author {
  margin-left: 4px;
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
}

.reaction.mine .reaction__author {
  color: var(--ctp-base);
}

.reaction.theirs .reaction__author {
  color: var(--ctp-subtext0);
}
  • Correctly uses design tokens (var(--ctp-*)) consistent with the rest of the file
  • white-space: nowrap prevents wrapping inside the pill — good for short names
  • Contrast looks appropriate: var(--ctp-base) on the blue .mine pill, var(--ctp-subtext0) on the surface .theirs pill

Minor observation: Node short names can be up to 4 chars (typical Meshtastic limit is 4 characters), but emoji short names (used when a node's short name is itself an emoji) could render in the author slot as an emoji sequence. white-space: nowrap handles this fine for single-emoji names; no issue here.


ChannelsTab.reactions.test.tsx

This is a solid test file for the new behavior. A few observations:

Positive:

  • The PR author verified the tests actually catch the regression (documented in the PR description with failure output) — this is exactly the right discipline
  • Mocking only the 3 hooks that have provider dependencies (useNodes, useSettings, useNotificationMuteSettings) rather than standing up the full provider tree is the right tradeoff here
  • ResizeObserver stub is correctly scoped locally rather than polluting the global test setup
  • The 4-test structure covers: inline render, emoji alongside name, tooltip preservation, and parent-not-shown-as-bubble

Observation on test fixture — src/components/ChannelsTab.reactions.test.tsx:109:

markMessagesAsRead: noop,

markMessagesAsRead in the component prop type (ChannelsTab.tsx:112-116) is typed as returning Promise<void>. The fixture supplies a synchronous noop. The cast as unknown as ChannelsTabProps on line 130 papers over the type mismatch. This won't cause test failures (the function isn't called during the test render), but it's a latent inconsistency. Consider using asyncNoop here for correctness:

markMessagesAsRead: asyncNoop,

Observation on test structure: The tests use document.querySelector directly rather than Testing Library's screen queries. This is intentional (the ChannelsTab.tsx component renders into document.body in the jsdom environment), and screen.getByText is used in the last test, showing the author is aware of both approaches. The direct DOM queries are fine for class-based assertions like .reaction__author.


Overall Assessment

Code quality — Clean, minimal implementation following existing patterns in the codebase
Correctness — Behavior matches the described intent; tooltip preserved, inline label added
CSS — Proper design token usage, consistent with the file's conventions
Tests — First test file for ChannelsTab; regression-verified; good coverage of the 4 key behaviors
⚠️ NitmarkMessagesAsRead: noop should be asyncNoop in the test fixture (not a bug, but a type inconsistency)
⚠️ Nit — The {reactorName && ...} guard is acknowledged defensive-only; acceptable as-is

This PR is ready to merge with just the minor nit above to consider. Good work establishing test coverage for ChannelsTab.


@Yeraze
Yeraze merged commit ebd8489 into main Jul 21, 2026
19 checks passed
@Yeraze
Yeraze deleted the feat/4243-reactor-short-name branch July 21, 2026 14:31
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.

[FEAT] Show reactor's short name next to emoji reactions in Channel Messages

1 participant