Chat header: two-zone toolbar (centered action cluster + right-side device zone)

Was a single right-aligned row where the title's flex-1 bunched the action
icons and the device controls together on the right. Now matches WorkClaw's
two-zone pattern:
- Identity stays left (capped width so it never reaches center).
- Action cluster (Sessions / New / Computer) is its own group, absolutely
  centered in the header (measured at ~808, the chat-header center).
- Device-size toggles + Close are a separate, absolutely-positioned
  top-right overlay (z-50, above the panel), shown only while the panel is
  open. Toggles are md+ only; Close stays (now also reachable above the
  mobile full-screen panel). Measured far-right at 1296–1420.
Button classes, icons, aria-labels and handlers are unchanged — only the
grouping/positioning wrappers differ. The panel card starts below the
header (pt-88), so both zones stay visible/clickable when the panel is open.

86 unit + 31 functional E2E green.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-11 07:41:15 -05:00
co-authored by Claude Fable 5
parent 40e7e66eea
commit 906ab50bb8
+23 -10
View File
@@ -8,9 +8,12 @@ import { Avatar } from "@/components/ui/Avatar";
import { DeviceSizeToggle } from "@/components/computer/DeviceSizeToggle"; import { DeviceSizeToggle } from "@/components/computer/DeviceSizeToggle";
import { panelParsers } from "@/lib/url/panel-params"; import { panelParsers } from "@/lib/url/panel-params";
/** Chat header (measured): 80px, transparent, identity left; sessions / /** Chat header (measured): a two-zone toolbar. The identity sits left; the
* new-session as 36px round icon buttons; the 42px coral-glow Computer * action cluster (Sessions / New / Computer) is centered as its own group;
* launcher on the right. Accessible names unchanged from §5b. */ * and the device-size toggles + Close are a separate, absolutely-positioned
* top-right overlay (z-50, above the Computer panel) shown while the panel
* is open. Button styling, icons, aria-labels and handlers are unchanged —
* only the grouping/positioning differs. */
export function ChatHeader({ export function ChatHeader({
agent, agent,
onNewSession, onNewSession,
@@ -23,7 +26,8 @@ export function ChatHeader({
}); });
const panelOpen = app !== null; const panelOpen = app !== null;
return ( return (
<header className="flex h-20 items-center gap-3 pr-6 pl-5"> <header className="relative flex h-20 items-center gap-3 px-5">
{/* Identity (left). Capped so it never reaches the centered cluster. */}
<Avatar <Avatar
name={agent.name} name={agent.name}
accent={agent.accent} accent={agent.accent}
@@ -31,12 +35,15 @@ export function ChatHeader({
shape="squircle" shape="squircle"
online={agent.status === "online"} online={agent.status === "online"}
/> />
<div className="min-w-0 flex-1"> <div className="min-w-0 max-w-[34%]">
<p className="truncate text-lg font-semibold text-neutral-200"> <p className="truncate text-lg font-semibold text-neutral-200">
{agent.name} {agent.name}
</p> </p>
<p className="truncate text-sm text-[#7d7d7d]">{agent.job_title}</p> <p className="truncate text-sm text-[#7d7d7d]">{agent.job_title}</p>
</div> </div>
{/* Action cluster — centered, standalone group. */}
<div className="absolute top-1/2 left-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center gap-3">
<button <button
type="button" type="button"
aria-label="Sessions" aria-label="Sessions"
@@ -66,15 +73,21 @@ export function ChatHeader({
> >
<Monitor aria-hidden size={20} /> <Monitor aria-hidden size={20} />
</button> </button>
{/* When the Computer is open, its size toggles + close live here in </div>
the global header (measured), beside a thin divider. */}
{/* Device-size controls + Close — a separate right-aligned overlay,
pinned top-right above the panel (z-50). The toggles only show at
md+; Close stays so it's reachable above the mobile full-screen
panel. */}
{panelOpen && ( {panelOpen && (
<> <div className="absolute top-1/2 right-5 z-50 flex -translate-y-1/2 items-center gap-1">
<span aria-hidden className="mx-1 h-5 w-px bg-border" /> <span aria-hidden className="mx-1 hidden h-5 w-px bg-border md:block" />
<div className="hidden md:flex">
<DeviceSizeToggle <DeviceSizeToggle
value={device} value={device}
onChange={(size) => setParams({ device: size })} onChange={(size) => setParams({ device: size })}
/> />
</div>
<button <button
type="button" type="button"
aria-label="Close computer" aria-label="Close computer"
@@ -83,7 +96,7 @@ export function ChatHeader({
> >
<X aria-hidden size={18} /> <X aria-hidden size={18} />
</button> </button>
</> </div>
)} )}
</header> </header>
); );