From 18fd48bfcb96db098a3bc393f152453740da4b26 Mon Sep 17 00:00:00 2001 From: Matthew Meszaros Date: Tue, 30 Jun 2026 10:34:14 +0200 Subject: [PATCH] feat: render teammate avatars with initials fallback on live cursors so it is always clear who each pointer belongs to --- .../components/app/presence/CanvasCursors.tsx | 2 +- web/src/components/app/presence/Cursor.tsx | 34 +++++++++++++++---- .../components/app/presence/GlobalCursors.tsx | 2 +- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/web/src/components/app/presence/CanvasCursors.tsx b/web/src/components/app/presence/CanvasCursors.tsx index 61370564..d126ea04 100644 --- a/web/src/components/app/presence/CanvasCursors.tsx +++ b/web/src/components/app/presence/CanvasCursors.tsx @@ -14,7 +14,7 @@ export default function CanvasCursors({ cursors }: { cursors: RemoteCursor[] }) return (
{cursors.map((c) => ( - + ))}
); diff --git a/web/src/components/app/presence/Cursor.tsx b/web/src/components/app/presence/Cursor.tsx index 917288c9..0f47dc7f 100644 --- a/web/src/components/app/presence/Cursor.tsx +++ b/web/src/components/app/presence/Cursor.tsx @@ -1,17 +1,29 @@ // Cursor — one teammate's live pointer, positioned in screen space by the layer -// that renders it (canvas or page). A short CSS transition smooths the gaps -// between throttled frames so movement reads as continuous. +// that renders it (canvas or page). The label carries the person's real avatar +// (initials fallback) next to their name so it is always clear who it is. A +// short CSS transition smooths the gaps between throttled frames. import React from "react"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; + +function initialsOf(name: string | null): string { + if (!name) return "?"; + const parts = name.trim().split(/\s+/).filter(Boolean); + if (parts.length === 0) return "?"; + if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase(); + return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); +} export default function Cursor({ color, name, + avatar, left, top, }: { color: string; name: string | null; + avatar?: string | null; left: number; top: number; }) { @@ -35,13 +47,21 @@ export default function Cursor({ strokeLinejoin="round" /> - {name ? ( - - {name} - + + {avatar ? : null} + + {initialsOf(name)} + + + {name ? ( + {name} + ) : null} + ) : null} ); diff --git a/web/src/components/app/presence/GlobalCursors.tsx b/web/src/components/app/presence/GlobalCursors.tsx index cb11eae1..9f139f8d 100644 --- a/web/src/components/app/presence/GlobalCursors.tsx +++ b/web/src/components/app/presence/GlobalCursors.tsx @@ -123,7 +123,7 @@ function GlobalCursorsOverlay({ scrollRef }: { scrollRef: React.RefObject {live.cursors.map((c) => ( - + ))} );