reply UX: show username + content preview instead of hash

- ComposeBox takes ReplyContext {id, username, content} instead of string
- Reply chip shows "^ benji message preview..." with truncation
- App passes full reply context from messagesById

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 21:14:35 +02:00
parent c0f48c5672
commit 1f9201dd03
2 changed files with 18 additions and 6 deletions

View File

@@ -43,7 +43,7 @@ export default function App() {
const [activeChannelId, setActiveChannelId] = useState<string | null>(null); const [activeChannelId, setActiveChannelId] = useState<string | null>(null);
const [messages, setMessages] = useState<Message[]>([]); const [messages, setMessages] = useState<Message[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [replyTo, setReplyTo] = useState<string | null>(null); const [replyTo, setReplyTo] = useState<{ id: string; username: string; content: string } | null>(null);
const [sheetOpen, setSheetOpen] = useState(false); const [sheetOpen, setSheetOpen] = useState(false);
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const prevMsgCountRef = useRef(0); const prevMsgCountRef = useRef(0);
@@ -178,7 +178,12 @@ export default function App() {
key={msg.id} key={msg.id}
message={msg} message={msg}
replyTarget={msg.reply_to ? messagesById.get(msg.reply_to) : undefined} replyTarget={msg.reply_to ? messagesById.get(msg.reply_to) : undefined}
onReply={setReplyTo} onReply={(id) => {
const target = messagesById.get(id);
if (target) {
setReplyTo({ id, username: target.user.display_name, content: target.content });
}
}}
/> />
)) ))
)} )}

View File

@@ -4,9 +4,15 @@ import type { User } from "@/types/User";
import { postMessage, getUsers, getCurrentUsername } from "@/api"; import { postMessage, getUsers, getCurrentUsername } from "@/api";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
interface ReplyContext {
id: string;
username: string;
content: string;
}
interface Props { interface Props {
channelId: string; channelId: string;
replyTo: string | null; replyTo: ReplyContext | null;
onClearReply: () => void; onClearReply: () => void;
onMessageSent: () => void; onMessageSent: () => void;
} }
@@ -95,7 +101,7 @@ export function ComposeBox({
await postMessage(channelId, { await postMessage(channelId, {
content: content.trim(), content: content.trim(),
type: msgType, type: msgType,
reply_to: replyTo ?? undefined, reply_to: replyTo?.id ?? undefined,
}); });
setContent(""); setContent("");
setMsgType("text"); setMsgType("text");
@@ -122,11 +128,12 @@ export function ComposeBox({
{replyTo && ( {replyTo && (
<div className="flex items-center gap-1.5 mb-1.5 text-[10px] font-mono"> <div className="flex items-center gap-1.5 mb-1.5 text-[10px] font-mono">
<span className="text-primary">^</span> <span className="text-primary">^</span>
<span className="text-muted-foreground">#{replyTo.slice(0, 8)}</span> <span className="text-foreground font-bold">{replyTo.username}</span>
<span className="text-muted-foreground truncate max-w-48 md:max-w-96">{replyTo.content}</span>
<button <button
type="button" type="button"
onClick={onClearReply} onClick={onClearReply}
className="text-muted-foreground hover:text-primary ml-1" className="text-muted-foreground hover:text-primary ml-auto flex-shrink-0"
> >
x x
</button> </button>