Merge pull request #41 from offendingcommit/feat/peer-display-name

feat(web): configurable peer display name
This commit is contained in:
Offending Commit
2026-05-28 17:07:50 -05:00
committed by GitHub
4 changed files with 145 additions and 9 deletions

View File

@@ -15,7 +15,11 @@ const KNOWN_SECTIONS = new Set(Object.keys(SECTION_LABELS));
type Segment = { label: string; href: string | null; mono?: boolean };
function buildSegments(pathname: string, mask: (v: string) => string): Segment[] {
function buildSegments(
pathname: string,
mask: (v: string) => string,
labels: Record<string, string>,
): Segment[] {
if (!pathname.startsWith("/workspaces")) return [];
const rest = pathname.slice("/workspaces".length); // "" | "/wid" | "/wid/peers" | ...
@@ -46,14 +50,20 @@ function buildSegments(pathname: string, mask: (v: string) => string): Segment[]
const subId = parts[2];
if (!subId) return segments;
// A friendly label override (e.g. a peer's display_name) renders in place of
// the raw id and drops the mono styling reserved for ids.
const override = labels[subId];
const subLabel = mask(override ?? subId);
const subMono = override === undefined;
if (parts.length === 3) {
segments.push({ label: mask(subId), href: null, mono: true });
segments.push({ label: subLabel, href: null, mono: subMono });
return segments;
}
segments.push({
label: mask(subId),
label: subLabel,
href: `/workspaces/${wid}/${section}/${subId}`,
mono: true,
mono: subMono,
});
const subSection = parts[3];
@@ -64,10 +74,10 @@ function buildSegments(pathname: string, mask: (v: string) => string): Segment[]
return segments;
}
export function Breadcrumb() {
export function Breadcrumb({ labels = {} }: { labels?: Record<string, string> } = {}) {
const { state } = useRouter();
const { mask } = useDemo();
const segments = buildSegments(state.location.pathname, mask);
const segments = buildSegments(state.location.pathname, mask, labels);
if (segments.length <= 1) return null;

View File

@@ -1,10 +1,12 @@
import { useNavigate, useParams } from "@tanstack/react-router";
import { AnimatePresence, motion } from "framer-motion";
import {
Check,
Eye,
EyeOff,
FlaskConical,
MessageCircle,
Pencil,
Save,
Search,
User,
@@ -19,6 +21,7 @@ import {
usePeerRepresentation,
useSearchPeer,
useSetPeerCard,
useUpdatePeer,
} from "@/api/queries";
import { Breadcrumb } from "@/components/layout/Breadcrumb";
import { Badge } from "@/components/shared/Badge";
@@ -41,6 +44,7 @@ import {
import { useDemo } from "@/hooks/useDemo";
import { useMetadata } from "@/hooks/useMetadata";
import { COLOR } from "@/lib/constants";
import { DISPLAY_NAME_KEY, hasDisplayName, peerDisplayName } from "@/lib/peerDisplay";
export function PeerDetail() {
const { mask } = useDemo();
@@ -69,6 +73,20 @@ export function PeerDetail() {
const [cardDraft, setCardDraft] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState("");
const peerMeta = (peer as { metadata?: Record<string, unknown> } | undefined)?.metadata;
const displayName = peerDisplayName(peerMeta, peerId);
const showsDisplayName = hasDisplayName(peerMeta, peerId);
const updatePeer = useUpdatePeer(workspaceId, peerId);
const [nameDraft, setNameDraft] = useState<string | null>(null);
function saveDisplayName() {
const next = (nameDraft ?? "").trim();
const merged: Record<string, unknown> = { ...(peerMeta ?? {}) };
if (next) merged[DISPLAY_NAME_KEY] = next;
else delete merged[DISPLAY_NAME_KEY];
updatePeer.mutate({ metadata: merged }, { onSuccess: () => setNameDraft(null) });
}
const observeMe = (peer as { configuration?: { observe_me?: boolean } } | undefined)
?.configuration?.observe_me;
@@ -81,14 +99,54 @@ export function PeerDetail() {
return (
<div className="page-container page-container--xl">
<motion.div initial={{ opacity: 0, y: -8 }} animate={{ opacity: 1, y: 0 }}>
<Breadcrumb />
<Breadcrumb labels={{ [peerId]: displayName }} />
<div className="flex items-start justify-between gap-4">
<div>
<div className="flex items-center gap-2 mb-1">
<User className="w-5 h-5" style={{ color: "var(--accent)" }} strokeWidth={1.5} />
<PageTitle className="font-mono break-all">{mask(peerId)}</PageTitle>
{observeMe !== undefined && (
{nameDraft === null ? (
<>
<PageTitle className={showsDisplayName ? "break-all" : "font-mono break-all"}>
{mask(displayName)}
</PageTitle>
<button
type="button"
onClick={() => setNameDraft(showsDisplayName ? displayName : "")}
className="shrink-0 p-1 rounded-md transition-colors hover:bg-[color:var(--surface)]"
style={{ color: "var(--text-4)" }}
title="Edit display name"
>
<Pencil className="w-3.5 h-3.5" strokeWidth={1.5} />
</button>
</>
) : (
<div className="flex items-center gap-1.5">
<Input
value={nameDraft}
onChange={(e) => setNameDraft(e.currentTarget.value)}
onKeyDown={(e) => {
if (e.key === "Enter") saveDisplayName();
if (e.key === "Escape") setNameDraft(null);
}}
placeholder="Display name"
autoFocus
className="w-56"
/>
<Button
variant="surface"
onClick={saveDisplayName}
disabled={updatePeer.isPending}
title="Save display name"
>
<Check className="w-3.5 h-3.5" strokeWidth={2} />
</Button>
<Button variant="surface" onClick={() => setNameDraft(null)} title="Cancel">
<X className="w-3.5 h-3.5" strokeWidth={2} />
</Button>
</div>
)}
{nameDraft === null && observeMe !== undefined && (
<span
className="inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full font-mono"
style={{
@@ -106,6 +164,9 @@ export function PeerDetail() {
</span>
)}
</div>
{showsDisplayName && nameDraft === null && (
<MonoCaption className="break-all">{mask(peerId)}</MonoCaption>
)}
<Body className="leading-none">Peer identity &amp; memory</Body>
</div>
<div className="flex items-center gap-2 shrink-0">

View File

@@ -0,0 +1,25 @@
/**
* Resolve a peer's friendly display name from its metadata, falling back to the
* raw peer id. Honcho peers are keyed by opaque ids (WhatsApp `…-lid`, UUIDs);
* a `display_name` metadata key lets the UI show something human-readable.
*/
export const DISPLAY_NAME_KEY = "display_name";
export function peerDisplayName(
metadata: Record<string, unknown> | null | undefined,
fallbackId: string,
): string {
const value = metadata?.[DISPLAY_NAME_KEY];
if (typeof value === "string" && value.trim().length > 0) {
return value.trim();
}
return fallbackId;
}
/** Whether a peer has an explicit display name distinct from its id. */
export function hasDisplayName(
metadata: Record<string, unknown> | null | undefined,
peerId: string,
): boolean {
return peerDisplayName(metadata, peerId) !== peerId;
}

View File

@@ -0,0 +1,40 @@
import { describe, expect, it } from "vitest";
import { hasDisplayName, peerDisplayName } from "@/lib/peerDisplay";
const PEER_ID = "22335577991-lid";
describe("peerDisplayName", () => {
it("returns the display_name when set", () => {
expect(peerDisplayName({ display_name: "Alice" }, PEER_ID)).toBe("Alice");
});
it("trims surrounding whitespace", () => {
expect(peerDisplayName({ display_name: " Bob " }, PEER_ID)).toBe("Bob");
});
it("falls back to the peer id when display_name is absent", () => {
expect(peerDisplayName({}, PEER_ID)).toBe(PEER_ID);
});
it("falls back to the peer id when display_name is blank", () => {
expect(peerDisplayName({ display_name: " " }, PEER_ID)).toBe(PEER_ID);
});
it("falls back to the peer id when display_name is not a string", () => {
expect(peerDisplayName({ display_name: 42 }, PEER_ID)).toBe(PEER_ID);
});
it("falls back to the peer id when metadata is null", () => {
expect(peerDisplayName(null, PEER_ID)).toBe(PEER_ID);
});
});
describe("hasDisplayName", () => {
it("is true when a distinct display name is set", () => {
expect(hasDisplayName({ display_name: "Alice" }, PEER_ID)).toBe(true);
});
it("is false when no display name is set", () => {
expect(hasDisplayName({}, PEER_ID)).toBe(false);
});
});