Computer panel: reserve chat space (--computer-width) so the chat condenses, not hides

The absolute panel overlaid the full-width centered chat column, hiding the
messages/composer behind it (WorkClaw avoids this by reserving the panel's
width as padding on the chat). Introduced a shared --computer-width var,
set once on the ChatWorkspace row per ?device= (phone 448px, tablet 67%,
full 100%; 0 when closed). Both the panel <aside> width and a new chat-
column wrapper's md:pr read it, so they never drift: opening the panel
squeezes the centered max-w-3xl column into the left space (re-centered)
while the panel overlays the reserved right area. Animated with
transition-[padding]/[width] + the signature easing for the 'condense'
effect. The header is outside the padded wrapper, so its cluster shift is
unaffected. Verified at 1440: composer right clears the panel left edge in
both tablet (561<593) and phone (920<992) — nothing hidden.

86 unit + 31 functional E2E green.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-11 12:17:52 -05:00
co-authored by Claude Fable 5
parent 914ffae8f9
commit 21da4dca19
2 changed files with 40 additions and 28 deletions
+27 -7
View File
@@ -1,7 +1,7 @@
"use client";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useState, type CSSProperties } from "react";
import { useQueryStates } from "nuqs";
import type { Agent } from "@/lib/api/schemas";
@@ -9,7 +9,17 @@ import type { Session } from "@/lib/api/sessions";
import { useChat } from "@/lib/gateway/use-chat";
import type { UiMessage } from "@/lib/gateway/transcript";
import { encodeSessionKeyParam } from "@/lib/url/session-key";
import { panelParsers } from "@/lib/url/panel-params";
import { panelParsers, type DeviceSize } from "@/lib/url/panel-params";
/* One source of truth for the Computer panel's width (measured): both the
panel <aside> (width) and the chat column (padding-right) read this var,
so the chat condenses into the left space instead of hiding behind the
panel — WorkClaw's reserve-space model. Collapses to 0 when closed. */
const COMPUTER_WIDTH: Record<DeviceSize, string> = {
phone: "448px",
tablet: "67%",
full: "100%",
};
import { SlidePanel } from "@/components/ui/SlidePanel";
import { SessionsColumn } from "@/components/sessions/SessionsColumn";
import { DevicePanel } from "@/components/computer/DevicePanel";
@@ -41,9 +51,11 @@ export function ChatWorkspace({
);
const [draft, setDraft] = useState("");
const empty = state.messages.length === 0;
const [{ sessions: showSessions }] = useQueryStates(panelParsers, {
shallow: true,
});
const [{ sessions: showSessions, app, device }] = useQueryStates(
panelParsers,
{ shallow: true },
);
const computerWidth = app !== null ? COMPUTER_WIDTH[device] : "0px";
function openSession(session: Session) {
router.push(
@@ -66,7 +78,10 @@ export function ChatWorkspace({
}
return (
<div className="relative flex h-dvh">
<div
className="relative flex h-dvh"
style={{ "--computer-width": computerWidth } as CSSProperties}
>
<SlidePanel open={showSessions} width={208} label="Sessions">
<SessionsColumn
sessions={sessions}
@@ -77,7 +92,11 @@ export function ChatWorkspace({
</SlidePanel>
<section className="flex min-w-0 flex-1 flex-col">
<ChatHeader agent={agent} onNewSession={newSession} />
<div className="mx-auto flex w-full max-w-3xl min-w-0 flex-1 flex-col overflow-hidden px-4">
{/* Reserve the panel's width as right-padding so the centered chat
column re-centers in the left space and never hides behind the
absolute panel (animates in sync with the panel). */}
<div className="flex min-h-0 flex-1 flex-col transition-[padding] duration-(--duration-normal) ease-app md:pr-[var(--computer-width)]">
<div className="mx-auto flex w-full max-w-3xl min-w-0 flex-1 flex-col overflow-hidden px-4">
{empty ? (
// Empty state: the composer is part of the centered welcome
// stack (avatar → heading → subtitle → composer → chips).
@@ -116,6 +135,7 @@ export function ChatWorkspace({
/>
</>
)}
</div>
</div>
</section>
<DevicePanel agent={agent} />