Left rail: horizontal rows, 44px avatars, left-aligned (WorkClaw)

Match WorkClaw's left sidebar (verified vs left-sidebar captures): claw rows
are horizontal — a 44px squircle avatar (was 58px, centered) on the left with
the name beside it, hugging the edge at px-4, gap-3. The "New claw" tile is a
bare 44px dashed + at the same inset (no label, like the reference). RosterList
drops items-center; collapsed (full-panel) mode still centers the avatars.

Avatar `rail` size 58px→44px (size-11) / radius 15px; test updated.
typecheck/lint(0)/86 unit/31 E2E/6 visual green; workspace-home baseline
regenerated.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-12 23:10:37 -05:00
co-authored by Claude Opus 4.8
parent 0923fb3f6a
commit 3a81be17d9
6 changed files with 14 additions and 22 deletions
@@ -21,14 +21,14 @@ export function AgentRosterItem({
onTogglePin?: (id: string) => void; onTogglePin?: (id: string) => void;
}) { }) {
return ( return (
<div className="group relative flex flex-col items-center"> <div className="group relative flex items-center">
<Link <Link
href={`/claws/${agent.id}`} href={`/claws/${agent.id}`}
aria-current={active ? "page" : undefined} aria-current={active ? "page" : undefined}
className="flex w-full flex-col items-center gap-1" className="flex w-full items-center gap-3 px-4 py-1 md:group-data-[collapsed=true]/rail:justify-center md:group-data-[collapsed=true]/rail:px-0"
> >
<span <span
className={`rounded-[22px] p-0.5 transition-colors duration-(--duration-normal) ease-app ${ className={`shrink-0 rounded-[17px] p-0.5 transition-colors duration-(--duration-normal) ease-app ${
active ? "ring-2 ring-coral" : "" active ? "ring-2 ring-coral" : ""
}`} }`}
> >
@@ -49,7 +49,7 @@ export function AgentRosterItem({
</span> </span>
</span> </span>
<span <span
className={`max-w-full truncate text-xs leading-[1.3] transition-colors duration-(--duration-normal) ease-app md:group-data-[collapsed=true]/rail:hidden ${ className={`min-w-0 flex-1 truncate text-sm leading-tight transition-colors duration-(--duration-normal) ease-app md:group-data-[collapsed=true]/rail:hidden ${
active active
? "font-semibold text-foreground" ? "font-semibold text-foreground"
: "font-medium text-muted-foreground group-hover:text-foreground" : "font-medium text-muted-foreground group-hover:text-foreground"
@@ -58,7 +58,7 @@ export function AgentRosterItem({
{agent.name} {agent.name}
</span> </span>
</Link> </Link>
<div className="md:group-data-[collapsed=true]/rail:hidden"> <div className="absolute top-1/2 right-1.5 -translate-y-1/2 md:group-data-[collapsed=true]/rail:hidden">
<AgentRowMenu <AgentRowMenu
agentId={agent.id} agentId={agent.id}
agentName={agent.name} agentName={agent.name}
+4 -12
View File
@@ -12,10 +12,6 @@ import { RosterList } from "./RosterList";
import { ShellNav } from "./ShellNav"; import { ShellNav } from "./ShellNav";
import { UserMenu } from "./UserMenu"; import { UserMenu } from "./UserMenu";
/* Labels hide (md+) when the rail collapses in full-panel mode. Applied to
every text node via the group/rail data flag on the aside. */
const HIDE_WHEN_COLLAPSED = "md:group-data-[collapsed=true]/rail:hidden";
interface LeftRailProps { interface LeftRailProps {
user: User; user: User;
roster: Agent[]; roster: Agent[];
@@ -90,18 +86,14 @@ export function LeftRail({ user, roster, creditsBalance }: LeftRailProps) {
)} )}
<Link <Link
href="/claws/new" href="/claws/new"
className="group mt-1 flex w-full flex-col items-center gap-1 py-1.5" aria-label="New claw"
className="group mt-0.5 flex items-center px-4 py-1 md:group-data-[collapsed=true]/rail:justify-center md:group-data-[collapsed=true]/rail:px-0"
> >
<span <span
aria-hidden aria-hidden
className="inline-flex size-12 items-center justify-center rounded-[17px] border border-dashed border-border text-coral transition-colors duration-(--duration-normal) ease-app group-hover:border-coral" className="inline-flex size-11 shrink-0 items-center justify-center rounded-[15px] border border-dashed border-border text-coral transition-colors duration-(--duration-normal) ease-app group-hover:border-coral"
> >
<Plus size={20} /> <Plus size={18} />
</span>
<span
className={`text-xs text-muted-foreground transition-colors duration-(--duration-normal) ease-app group-hover:text-foreground ${HIDE_WHEN_COLLAPSED}`}
>
New claw
</span> </span>
</Link> </Link>
</div> </div>
+1 -1
View File
@@ -51,7 +51,7 @@ export function RosterList({ roster }: { roster: Agent[] }) {
}); });
return ( return (
<ul aria-label="Your claws" className="flex flex-col items-center gap-1"> <ul aria-label="Your claws" className="flex flex-col gap-0.5">
{ordered.map((agent) => ( {ordered.map((agent) => (
<li key={agent.id} className="w-full"> <li key={agent.id} className="w-full">
<AgentRosterItem <AgentRosterItem
+2 -2
View File
@@ -14,9 +14,9 @@ describe("Avatar", () => {
<Avatar name="Scout" shape="squircle" size="rail" />, <Avatar name="Scout" shape="squircle" size="rail" />,
); );
const span = container.querySelector("span[aria-hidden]")!; const span = container.querySelector("span[aria-hidden]")!;
expect(span.className).toContain("rounded-[20px]"); expect(span.className).toContain("rounded-[15px]");
expect(span.className).not.toContain("rounded-full"); expect(span.className).not.toContain("rounded-full");
expect(span.className).toContain("size-[58px]"); /* 58px rail tile */ expect(span.className).toContain("size-11"); /* 44px rail tile */
}); });
it("welcome size renders the 126px hero avatar", () => { it("welcome size renders the 126px hero avatar", () => {
+2 -2
View File
@@ -13,7 +13,7 @@ const SIZES = {
sm: "size-6 text-xxs", sm: "size-6 text-xxs",
md: "size-8 text-xs", md: "size-8 text-xs",
lg: "size-16 text-xl", lg: "size-16 text-xl",
rail: "size-[58px] text-xl" /* 58px claw rail tile */, rail: "size-11 text-base" /* 44px claw rail tile (WorkClaw) */,
chat: "size-[50px] text-base" /* assistant message avatar */, chat: "size-[50px] text-base" /* assistant message avatar */,
header: "size-[58px] text-2xl" /* chat-header identity */, header: "size-[58px] text-2xl" /* chat-header identity */,
settings: "size-24 text-3xl" /* 96px Settings hero (measured) */, settings: "size-24 text-3xl" /* 96px Settings hero (measured) */,
@@ -23,7 +23,7 @@ const SIZES = {
/* Squircle radius scales with the tile so big avatars read as rounded as /* Squircle radius scales with the tile so big avatars read as rounded as
the 48px rail tile (a flat 17px looks square at 126px). */ the 48px rail tile (a flat 17px looks square at 126px). */
const SQUIRCLE_RADIUS: Partial<Record<keyof typeof SIZES, string>> = { const SQUIRCLE_RADIUS: Partial<Record<keyof typeof SIZES, string>> = {
rail: "rounded-[20px]", rail: "rounded-[15px]",
header: "rounded-[20px]", header: "rounded-[20px]",
settings: "rounded-[34px]", settings: "rounded-[34px]",
welcome: "rounded-[40px]", welcome: "rounded-[40px]",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB