feat(channels): add thread @-mentions and an Activity page#3158
feat(channels): add thread @-mentions and an Activity page#3158k11kirky wants to merge 3 commits into
Conversation
Thread composers in the Channels space get an @-mention typeahead over the org's members; mentions are stored as inline @[Name](email) tokens in the message content, so no message schema change is needed. A new Activity page (nav item with unread badge in the channels sidebar) lists mentions of the viewer across channel threads, derived client-side from the task feeds and threads already polled, and opens the thread via the channel deep-link route. Generated-By: PostHog Code Task-Id: a2ff8500-aaa6-4ce0-b01a-656d3b2c3b61
|
React Doctor found 1 issue in 1 file · 1 warning. 1 warning
Reviewed by React Doctor for commit |
|
Reviews (1): Last reviewed commit: "feat(channels): add thread @-mentions an..." | Re-trigger Greptile |
| export function applyMention( | ||
| text: string, | ||
| active: ActiveMentionQuery, | ||
| caret: number, | ||
| member: UserBasic, | ||
| ): { text: string; caret: number } { | ||
| const token = `${formatMention(userDisplayName(member), member.email)} `; | ||
| const before = text.slice(0, active.start); | ||
| const next = before + token + text.slice(caret); | ||
| return { text: next, caret: before.length + token.length }; |
There was a problem hiding this comment.
Leftover query characters when caret moves backward mid-query
applyMention replaces from active.start to caret, where caret = el.selectionStart and active.query = text.slice(active.start + 1, caret) — these are always equal by construction. The invariant breaks when the user presses ← to move the cursor backward while the popup is still open: syncActive fires and shortens active.query, but the original typed characters after the new caret remain in the text.
Concrete failure: text is "hey @raq", user presses ← (caret moves from 8 to 7), popup still shows Raquel because "ra" still matches, user presses Tab. applyMention("hey @raq", { start:4, query:"ra" }, 7, raquel) slices text.slice(7) = "q" into the tail → message becomes "hey @[Raquel Smith](raquel@posthog.com) q".
A clean fix is to track the furthest caret position seen for the current @-trigger (a tokenEnd field on ActiveMentionQuery, updated in syncActive to Math.max(caret, prev.tokenEnd) whenever prev.start === newActive.start), and use active.tokenEnd in insert instead of el.selectionStart as the replacement end.
| const token = `${formatMention(userDisplayName(member), member.email)} `; | ||
| const before = text.slice(0, active.start); | ||
| const next = before + token + text.slice(caret); |
There was a problem hiding this comment.
Double-space when inserting a mention mid-text:
token always carries a trailing space, and when text.slice(caret) already starts with a space (i.e. the caret sits immediately before existing text like " can you look"), the result has two consecutive spaces. The existing test at line 1542 asserts this double-space output but the effect is visible in messages. Stripping the trailing space when the next character is already a space avoids it.
| const token = `${formatMention(userDisplayName(member), member.email)} `; | |
| const before = text.slice(0, active.start); | |
| const next = before + token + text.slice(caret); | |
| const tail = text.slice(caret); | |
| const base = formatMention(userDisplayName(member), member.email); | |
| const token = tail.startsWith(" ") ? `${base}` : `${base} `; | |
| const before = text.slice(0, active.start); | |
| const next = before + token + tail; |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
…mposer applyMention now replaces the whole @word (caret moved backward mid-query no longer leaks typed characters into the message) and reuses an existing following space instead of doubling it. MentionComposer drops its state-sync effects for ref-guarded render-time adjustments and scrolls in the key handler, clearing react-doctor's no-adjust-state-on-prop-change errors. MentionText keys segments by character offset instead of array index. Generated-By: PostHog Code Task-Id: a2ff8500-aaa6-4ce0-b01a-656d3b2c3b61
Generated-By: PostHog Code Task-Id: a2ff8500-aaa6-4ce0-b01a-656d3b2c3b61
Note
Stacked on #3157 (channel/thread deep links) — review that first; this PR targets its branch and reuses its thread deep-link routing.
Problem
Channel threads (project-bluebird) are multiplayer, but there's no way to pull a teammate into one — you can't @-tag anyone, and nothing tells you a thread needs your attention.
Why
Requested for Bluebird channels: tag any member of your org in a thread and have them see the notification in a new Activity page in the top-left nav, built on the deep links from #3157.
Changes
@in a thread's "Reply in thread…" box opens a typeahead over the org's members (new/api/organizations/@current/members/client call). Selecting one inserts an inline@[Name](email)token, so mentions ride in existing message content with no backend change; older clients degrade to readable text. Mentions render as highlighted chips, with a stronger highlight when it's you./website/activityroute plus an Activity item (with unread red-dot badge) in the channels sidebar nav. It lists thread messages across all channels that mention you, newest first; clicking one opens the thread through the channel deep-link route from feat(channels): add shareable deep links for channels and threads #3157, and each row offers the shareable thread link via the same copy-link helper.Channel action(mention_member,view_activity,open_mention,nav_click). Everything lives inside the Channels space, which is gated by theproject-bluebirdflag at the route.How did you test this?
canvas(76),deep-links/router(9), and corecanvas(58) suites — all pass.pnpm typecheck(22/22) and Biome clean.Automatic notifications
Created with PostHog Code