feat(channels): add thread @-mentions and an Activity page#3158
feat(channels): add thread @-mentions and an Activity page#3158k11kirky wants to merge 7 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 5 issues in 1 file · 5 warnings. 5 warnings
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
…s to the channel message The thread composer becomes a compact tiptap editor: selecting a teammate from the @-typeahead now inserts a styled mention chip (like the task composer's chips) instead of showing the raw @[Name](email) token, while the draft still round-trips through the inline token format so storage and notification detection are unchanged. Mention links land on the message instead of the task: Activity rows and shared thread links now open the channel feed scrolled to the task's message with its thread panel open (a `thread` search param on the channel route), rather than deep-linking to the task detail view. Generated-By: PostHog Code Task-Id: 4daaf463-a6c2-4fb6-bed6-ee055729cc1d
…fan-out Cross-repo plan (posthog backend + this repo) to move mention aggregation server-side: a write-time mention index and a single task_mentions endpoint, collapsing useMentionActivity to one query. Fixes both the polling scale problem and the hooks-orchestrating-multiple-queries rule violation. Generated-By: PostHog Code Task-Id: 4daaf463-a6c2-4fb6-bed6-ee055729cc1d
…plan Generated-By: PostHog Code Task-Id: 4daaf463-a6c2-4fb6-bed6-ee055729cc1d
useMentionActivity now wraps a single getTaskMentions query against the new task_mentions endpoint; the per-channel/per-task thread scan, its mention-matching helpers, and the plan file are deleted. mentionActivity in core is reduced to a DTO-to-item mapper (countUnseenActivity unchanged), and ActivityView resolves desktop folder-channel ids itself. Generated-By: PostHog Code Task-Id: 47e3ad2f-36c4-4d54-ae0c-07923d306818
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