From 8ce8ebc9b8630be931f832043cf0645e2bf415fa Mon Sep 17 00:00:00 2001 From: limiteinductive Date: Sun, 29 Mar 2026 20:56:45 +0200 Subject: [PATCH] =?UTF-8?q?add=20@mentions=20=E2=80=94=20parsed=20server-s?= =?UTF-8?q?ide,=20rendered=20as=20highlighted=20spans?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Backend: parse_mentions() extracts @username from content - Message API response includes mentions: string[] field - Frontend: renderContent() highlights @mentions in hot orange - Works with alphanumeric + hyphens + underscores Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/colony-types/src/lib.rs | 1 + crates/colony/src/db.rs | 11 +++++++++++ ui/colony/src/components/MessageItem.tsx | 17 ++++++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/colony-types/src/lib.rs b/crates/colony-types/src/lib.rs index 97f3ac7..58ca326 100644 --- a/crates/colony-types/src/lib.rs +++ b/crates/colony-types/src/lib.rs @@ -44,6 +44,7 @@ pub struct Message { pub content: String, #[ts(optional)] pub metadata: Option, + pub mentions: Vec, #[ts(optional)] pub reply_to: Option, pub created_at: DateTime, diff --git a/crates/colony/src/db.rs b/crates/colony/src/db.rs index ce46580..9b634e8 100644 --- a/crates/colony/src/db.rs +++ b/crates/colony/src/db.rs @@ -2,6 +2,16 @@ use chrono::{DateTime, Utc}; use sqlx::FromRow; use uuid::Uuid; +/// Extract @mentions from message content +fn parse_mentions(content: &str) -> Vec { + content + .split_whitespace() + .filter(|w| w.starts_with('@') && w.len() > 1) + .map(|w| w.trim_start_matches('@').trim_end_matches(|c: char| !c.is_alphanumeric() && c != '_' && c != '-').to_string()) + .filter(|m| !m.is_empty()) + .collect() +} + /// DB row types — these map directly to SQL tables. /// Separate from API types in colony-types. @@ -87,6 +97,7 @@ impl MessageRow { } else { self.content.clone() }, + mentions: parse_mentions(&self.content), metadata: self .metadata .as_ref() diff --git a/ui/colony/src/components/MessageItem.tsx b/ui/colony/src/components/MessageItem.tsx index 72e2996..cbeed0b 100644 --- a/ui/colony/src/components/MessageItem.tsx +++ b/ui/colony/src/components/MessageItem.tsx @@ -26,6 +26,21 @@ function timeAgo(dateStr: string): string { return `${Math.floor(hrs / 24)}d`; } +function renderContent(text: string) { + // Split on @mentions and render them as highlighted spans + const parts = text.split(/(@[\w-]+)/g); + return parts.map((part, i) => { + if (part.startsWith("@")) { + return ( + + {part} + + ); + } + return part; + }); +} + export function MessageItem({ message, replyTarget, onReply }: Props) { const [metaOpen, setMetaOpen] = useState(false); const isAgent = message.user.role === "agent"; @@ -110,7 +125,7 @@ export function MessageItem({ message, replyTarget, onReply }: Props) { {isDeleted ? ( [deleted] ) : ( - message.content + renderContent(message.content) )}