@@ -286,7 +278,7 @@ function SidebarUserSection({ collapsed }: { collapsed: boolean }) {
@@ -313,7 +305,7 @@ function SidebarUserSection({ collapsed }: { collapsed: boolean }) {
diff --git a/src/components/layout/top-bar.tsx b/src/components/layout/top-bar.tsx
index 4d499d1a2..53c220210 100644
--- a/src/components/layout/top-bar.tsx
+++ b/src/components/layout/top-bar.tsx
@@ -26,7 +26,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Skeleton } from "@/components/ui/skeleton";
import Link from "next/link";
import { useAuth, useLogout } from "@/hooks/use-auth";
-import { accountName } from "@/lib/account-name";
+import { accountName, nameInitials } from "@/lib/account-name";
import { useRecordCapabilities } from "@/hooks/use-record-capabilities";
import { useTheme } from "@/components/providers";
import { useTranslations } from "@/lib/i18n/context";
@@ -41,14 +41,6 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
-function getInitials(name: string): string {
- return name
- .split(/[\s._-]+/)
- .slice(0, 2)
- .map((s) => s[0]?.toUpperCase() ?? "")
- .join("");
-}
-
export function TopBar() {
const { user, isLoading } = useAuth();
// v1.36.0 — acting on somebody else's record; see the menu below. Read from
@@ -147,7 +139,7 @@ export function TopBar() {
)}
- {getInitials(accountName(user))}
+ {nameInitials(accountName(user))}
{accountName(user)}
diff --git a/src/components/settings/account-section/__tests__/avatar-initials.test.tsx b/src/components/settings/account-section/__tests__/avatar-initials.test.tsx
new file mode 100644
index 000000000..0e9bd00a3
--- /dev/null
+++ b/src/components/settings/account-section/__tests__/avatar-initials.test.tsx
@@ -0,0 +1,39 @@
+/**
+ * The avatar fallback on the settings card spells the name the rest of the
+ * app shows. It used to take its letters from the login, so an account named
+ * "Robin Example" with the login "rex" read "RE" in the top bar and the
+ * sidebar and "R" here.
+ */
+import { describe, expect, it, vi } from "vitest";
+import { renderToStaticMarkup } from "react-dom/server";
+
+vi.mock("@tanstack/react-query", () => ({
+ useMutation: () => ({ mutate: vi.fn(), isPending: false }),
+}));
+
+vi.mock("@/hooks/use-auth", () => ({
+ useAuth: () => ({
+ user: {
+ id: "u1",
+ username: "rex",
+ displayName: "Robin Example",
+ avatarUrl: null,
+ },
+ refetch: vi.fn(),
+ }),
+}));
+
+import { I18nProvider } from "@/lib/i18n/context";
+import { AvatarSection } from "../avatar-section";
+
+describe("the settings avatar fallback", () => {
+ it("builds its initials from the display name, not the login", () => {
+ const html = renderToStaticMarkup(
+
+
+ ,
+ );
+ expect(html).toContain(">RE<");
+ expect(html).not.toContain(">R<");
+ });
+});
diff --git a/src/components/settings/account-section/avatar-section.tsx b/src/components/settings/account-section/avatar-section.tsx
index bff3eb9e5..6297cdb61 100644
--- a/src/components/settings/account-section/avatar-section.tsx
+++ b/src/components/settings/account-section/avatar-section.tsx
@@ -13,6 +13,7 @@ import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { apiFetchRaw } from "@/lib/api/api-fetch";
import { statusText, type StatusMessage } from "./account-section-utils";
+import { accountName, nameInitials } from "@/lib/account-name";
// Accepted upload types + byte cap mirror the server contract in
// `src/app/api/user/avatar/route.ts` so the client rejects an obvious
@@ -21,14 +22,6 @@ import { statusText, type StatusMessage } from "./account-section-utils";
const ACCEPTED_AVATAR_TYPES = ["image/jpeg", "image/png", "image/webp"];
const AVATAR_MAX_UPLOAD_BYTES = 2 * 1024 * 1024;
-function getAvatarInitials(name: string): string {
- return name
- .split(/[\s._-]+/)
- .slice(0, 2)
- .map((s) => s[0]?.toUpperCase() ?? "")
- .join("");
-}
-
export function AvatarSection() {
const { t } = useTranslations();
const { user, refetch } = useAuth();
@@ -123,9 +116,9 @@ export function AvatarSection() {
- {avatarUrl && }
+ {avatarUrl && }
- {getAvatarInitials(user.username)}
+ {nameInitials(accountName(user))}