Skip to content

Commit 438cfbe

Browse files
authored
style: aesthetic polish for workspace shell, transcript, and home pane (#76)
Co-authored-by: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com>
1 parent 43c2721 commit 438cfbe

13 files changed

Lines changed: 82 additions & 44 deletions

File tree

apps/web/src/components/HomePane.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
EmptyMedia,
1616
EmptyTitle,
1717
} from "@t4-code/ui";
18+
import { History } from "lucide-react";
1819
import { useEffect, useMemo, useSyncExternalStore } from "react";
1920

2021
import { desktopRuntime, useDesktopRuntimeSnapshot } from "../platform/desktop-runtime.ts";
@@ -290,8 +291,8 @@ function BrowserHomePane({ railOverlaid }: { railOverlaid: boolean }) {
290291
</div>
291292
<Empty className="flex-1 border-0">
292293
<EmptyHeader>
293-
<EmptyMedia variant="default">
294-
<BrandLockup byline size="lg" />
294+
<EmptyMedia variant="icon">
295+
<History aria-hidden="true" className="size-5 text-muted-foreground" />
295296
</EmptyMedia>
296297
<EmptyTitle>Pick up where a session left off</EmptyTitle>
297298
<EmptyDescription>
@@ -317,7 +318,7 @@ function BrowserHomePane({ railOverlaid }: { railOverlaid: boolean }) {
317318
Search sessions
318319
</Button>
319320
</div>
320-
<p className="text-muted-foreground text-xs">
321+
<p className="text-muted-foreground text-sm">
321322
{modKey}K searches, {modKey}1 to {modKey}9 jump straight to a session.
322323
</p>
323324
</EmptyContent>

apps/web/src/components/SessionScreen.tsx

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import {
1010
Sheet,
1111
SheetPopup,
1212
StatusPill,
13+
Tooltip,
14+
TooltipPopup,
15+
TooltipTrigger,
1316
useReducedMotion,
1417
} from "@t4-code/ui";
1518
import { Popover } from "@base-ui/react/popover";
@@ -400,25 +403,29 @@ export function SessionScreen({
400403
className="flex min-h-0 shrink-0 flex-col bg-(--sidebar-background)"
401404
style={{ width: `min(${paneWidth}px, 42vw)` }}
402405
>
403-
<div className="surface-subheader gap-2 px-3">
404-
<span className="font-medium text-xs">{activeMeta.label}</span>
405-
<span className="flex-1" />
406-
<IconButton
407-
aria-label={`Close ${activeMeta.label}`}
408-
onClick={() => workspaceStore.getState().setPaneOpen(session.id, false)}
409-
size="icon-xs"
410-
>
411-
<X />
412-
</IconButton>
413-
</div>
414406
<ScrollArea className="min-h-0 flex-1">
415407
<div className="pane-content-enter" key={viewPaneFamily}>
416-
<PaneContent family={viewPaneFamily} />
408+
<PaneContent
409+
family={viewPaneFamily}
410+
trailing={
411+
<Tooltip>
412+
<TooltipTrigger
413+
render={
414+
<IconButton
415+
aria-label={`Close ${activeMeta.label}`}
416+
onClick={() => workspaceStore.getState().setPaneOpen(session.id, false)}
417+
size="icon-xs"
418+
>
419+
<X />
420+
</IconButton>
421+
}
422+
/>
423+
<TooltipPopup side="bottom">Close (Esc)</TooltipPopup>
424+
</Tooltip>
425+
}
426+
/>
417427
</div>
418428
</ScrollArea>
419-
<p className="border-border border-t px-3 py-2 text-muted-foreground text-xs">
420-
Esc closes this panel.
421-
</p>
422429
</aside>
423430
</div>
424431
)}
@@ -430,9 +437,6 @@ export function SessionScreen({
430437
open={viewPaneOpen}
431438
>
432439
<SheetPopup aria-label={activeMeta.label} side="right">
433-
<div className="surface-subheader gap-2 px-3">
434-
<span className="font-medium text-xs">{activeMeta.label}</span>
435-
</div>
436440
<PaneContent family={viewPaneFamily} />
437441
</SheetPopup>
438442
</Sheet>

apps/web/src/features/composer/Composer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,8 @@ export function Composer({
532532
onIntent={onIntent}
533533
/>
534534
<span className="min-w-0 flex-1" />
535+
<ContextMeter usedTokens={contextUsedTokens} windowTokens={contextWindowTokens} />
536+
<span className="mx-1 h-4 w-px shrink-0 bg-border" aria-hidden="true" />
535537
<Tooltip>
536538
<TooltipTrigger
537539
render={
@@ -554,7 +556,6 @@ export function Composer({
554556
"This host does not support image prompts yet")}
555557
</TooltipPopup>
556558
</Tooltip>
557-
<ContextMeter usedTokens={contextUsedTokens} windowTokens={contextWindowTokens} />
558559
{turnActive && (
559560
<>
560561
<Tooltip>

apps/web/src/features/panes/ActivityPane.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
X,
2020
} from "lucide-react";
2121
import { memo, useMemo, useRef, useState, type ComponentType } from "react";
22+
import type * as React from "react";
2223

2324
import { useMediaQuery } from "../../hooks/useMediaQuery.ts";
2425
import { exportActivity, redactPayload, selectVisibleActivity } from "./activity-log.ts";
@@ -183,7 +184,7 @@ function ActivityInspector({
183184
);
184185
}
185186

186-
export function ActivityPane({ api }: { readonly api: InspectorStoreApi }) {
187+
export function ActivityPane({ api, trailing }: { readonly api: InspectorStoreApi; readonly trailing?: React.ReactNode | undefined }) {
187188
const entries = useInspector(api, (state) => state.activity);
188189
const filter = useInspector(api, (state) => state.activityFilter);
189190
const query = useInspector(api, (state) => state.activityQuery);
@@ -229,6 +230,7 @@ export function ActivityPane({ api }: { readonly api: InspectorStoreApi }) {
229230
<PaneHeading
230231
family="activity"
231232
summary={`${entries.length} recorded${pausedAtSeq !== null ? " · paused" : ""}`}
233+
trailing={trailing}
232234
/>
233235
<div className="flex shrink-0 flex-wrap items-center gap-1 border-border border-b px-2 py-1.5">
234236
<div

apps/web/src/features/panes/AgentsPane.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
DialogTitle,
1414
} from "@t4-code/ui";
1515
import { memo, useCallback, useMemo, useRef, useState } from "react";
16+
import type * as React from "react";
1617

1718
import type { TranscriptImageSource } from "../session-runtime/transcript-images.ts";
1819
import { TranscriptRowContent } from "../transcript/TranscriptRows.tsx";
@@ -103,7 +104,7 @@ const AgentRow = memo(function AgentRow({
103104
className={cn(
104105
"flex min-h-11 cursor-pointer flex-col justify-center gap-0.5 rounded-md pe-2 py-1.5 outline-none transition-colors duration-(--motion-duration-fast) sm:min-h-0",
105106
selected ? "bg-secondary" : "hover:bg-secondary/60",
106-
focused && "ring-2 ring-ring ring-offset-1 ring-offset-background",
107+
focused && "group-focus-visible/tree:ring-2 group-focus-visible/tree:ring-ring group-focus-visible/tree:ring-offset-1 group-focus-visible/tree:ring-offset-background",
107108
)}
108109
id={`agent-row-${id}`}
109110
onClick={() => onSelect(id)}
@@ -422,10 +423,12 @@ export function AgentsPane({
422423
api,
423424
sessionId,
424425
imageSource,
426+
trailing,
425427
}: {
426428
readonly api: InspectorStoreApi;
427429
readonly sessionId: string;
428430
readonly imageSource?: TranscriptImageSource | undefined;
431+
readonly trailing?: React.ReactNode | undefined;
429432
}) {
430433
// Structure fingerprint: rows rebuild only when membership/nesting change,
431434
// never on progress or state patches.
@@ -469,11 +472,11 @@ export function AgentsPane({
469472

470473
return (
471474
<div className="flex h-full min-h-0 flex-col">
472-
<PaneHeading family="agents" summary={summary} />
475+
<PaneHeading family="agents" summary={summary} trailing={trailing} />
473476
<div
474477
aria-activedescendant={`agent-row-${rows[activeIndex]?.id ?? ""}`}
475478
aria-label="Agents in this session"
476-
className="min-h-0 flex-1 overflow-y-auto p-1.5 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
479+
className="group/tree min-h-0 flex-1 overflow-y-auto p-1.5 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
477480
onKeyDown={(event) => {
478481
if (event.key === "ArrowDown") {
479482
event.preventDefault();

apps/web/src/features/panes/FilesPane.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { Badge, Button, cn, Skeleton } from "@t4-code/ui";
55
import { ChevronRight, FileText, Folder, ImageIcon, WifiOff } from "lucide-react";
66
import { useEffect, useMemo } from "react";
7+
import type * as React from "react";
78

89
import { FamilyEmpty } from "./FamilyEmpty.tsx";
910
import { PaneHeading } from "./PaneHeading.tsx";
@@ -246,7 +247,7 @@ function EditorBody({
246247
);
247248
}
248249

249-
export function FilesPane({ api }: { readonly api: InspectorStoreApi }) {
250+
export function FilesPane({ api, trailing }: { readonly api: InspectorStoreApi; readonly trailing?: React.ReactNode | undefined }) {
250251
const query = useInspector(api, (state) => state.files.query);
251252
const selectedPath = useInspector(api, (state) => state.files.selectedPath);
252253
const preview = useInspector(api, (state) => state.files.preview);
@@ -269,7 +270,7 @@ export function FilesPane({ api }: { readonly api: InspectorStoreApi }) {
269270
if (offline) {
270271
return (
271272
<div className="flex h-full min-h-0 flex-col">
272-
<PaneHeading family="files" summary="host unreachable" />
273+
<PaneHeading family="files" summary="host unreachable" trailing={trailing} />
273274
<FamilyEmpty family="files" />
274275
<p className="border-border border-t px-3 py-2 text-muted-foreground text-xs">
275276
This session's host is offline. Files return when the host does.
@@ -280,7 +281,7 @@ export function FilesPane({ api }: { readonly api: InspectorStoreApi }) {
280281

281282
return (
282283
<div className="flex h-full min-h-0 flex-col">
283-
<PaneHeading family="files" summary={selectedPath ?? undefined} />
284+
<PaneHeading family="files" summary={selectedPath ?? undefined} trailing={trailing} />
284285
<div className="shrink-0 border-border border-b px-2 py-1.5">
285286
<input
286287
aria-label="Search loaded files"

apps/web/src/features/panes/PaneContent.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Right-pane family body: routes the active family to its panel, bound to
22
// the active session's inspector store. The shell owns the frame; this seam
33
// owns everything inside it.
4+
import type * as React from "react";
45
import { FamilyEmpty } from "./FamilyEmpty.tsx";
56
import { desktopRuntime } from "../../platform/desktop-runtime.ts";
67
import { rendererPlatform, useWorkspace } from "../../state/store-instance.ts";
@@ -70,10 +71,12 @@ if (rendererPlatform.mode === "browser") {
7071

7172
export interface PaneContentProps {
7273
readonly family: PaneFamily;
74+
/** Optional trailing action forwarded to the pane heading (e.g. dock close button). */
75+
readonly trailing?: React.ReactNode | undefined;
7376
}
7477

7578

76-
export function PaneContent({ family }: PaneContentProps) {
79+
export function PaneContent({ family, trailing }: PaneContentProps) {
7780
const sessionId = useWorkspace((state) => state.activeSessionId);
7881
const store = sessionId === null ? null : getInspectorStore(sessionId);
7982
if (sessionId === null || store === null) return <FamilyEmpty family={family} />;
@@ -86,15 +89,15 @@ export function PaneContent({ family }: PaneContentProps) {
8689
address === null
8790
? undefined
8891
: (transcriptImageSourceForSession(address.hostId, address.sessionId) ?? undefined);
89-
return <AgentsPane api={store} imageSource={imageSource} sessionId={sessionId} />;
92+
return <AgentsPane api={store} imageSource={imageSource} sessionId={sessionId} trailing={trailing} />;
9093
}
9194
case "activity":
92-
return <ActivityPane api={store} />;
95+
return <ActivityPane api={store} trailing={trailing} />;
9396
case "review":
94-
return <ReviewPane api={store} />;
97+
return <ReviewPane api={store} trailing={trailing} />;
9598
case "files":
96-
return <FilesPane api={store} />;
99+
return <FilesPane api={store} trailing={trailing} />;
97100
case "terminals":
98-
return <TerminalsPane api={store} sessionId={sessionId} />;
101+
return <TerminalsPane api={store} sessionId={sessionId} trailing={trailing} />;
99102
}
100103
}

apps/web/src/features/panes/PaneHeading.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Panel heading: every family body opens with its own name in text, so the
22
// active family is unmistakable even when the shell's icon toggles are
33
// ambiguous (200% zoom, narrow sheets, screen readers).
4+
import type * as React from "react";
45
import { cn } from "@t4-code/ui";
56

67
import { PANE_FAMILY_META } from "../../components/pane-families.tsx";
@@ -10,19 +11,22 @@ export function PaneHeading({
1011
family,
1112
summary,
1213
className,
14+
trailing,
1315
}: {
1416
readonly family: PaneFamily;
1517
/** One short live fact ("6 agents · 2 running"); plain language. */
1618
readonly summary?: string | undefined;
17-
readonly className?: string;
19+
readonly className?: string | undefined;
20+
/** Optional trailing action rendered at the row's end (e.g. the dock close button). */
21+
readonly trailing?: React.ReactNode | undefined;
1822
}) {
1923
const meta = PANE_FAMILY_META.find((entry) => entry.id === family);
2024
if (meta === undefined) return null;
2125
const Icon = meta.icon;
2226
return (
2327
<h2
2428
className={cn(
25-
"flex shrink-0 items-center gap-1.5 border-border border-b px-3 py-1.5",
29+
"flex shrink-0 items-center gap-1.5 border-border border-b px-3 py-1.5 in-[[data-slot=sheet-popup]]:pe-14",
2630
className,
2731
)}
2832
>
@@ -33,6 +37,9 @@ export function PaneHeading({
3337
{summary}
3438
</span>
3539
)}
40+
{trailing !== undefined && (
41+
<span className="ms-auto flex shrink-0 items-center gap-1">{trailing}</span>
42+
)}
3643
</h2>
3744
);
3845
}

apps/web/src/features/panes/ReviewPane.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from "@t4-code/ui";
1515
import { Check, MessageSquarePlus, WrapText, X } from "lucide-react";
1616
import { Fragment, useMemo, useState } from "react";
17+
import type * as React from "react";
1718

1819
import { FamilyEmpty } from "./FamilyEmpty.tsx";
1920
import { PaneHeading } from "./PaneHeading.tsx";
@@ -383,7 +384,7 @@ function FileBody({
383384
);
384385
}
385386

386-
export function ReviewPane({ api }: { readonly api: InspectorStoreApi }) {
387+
export function ReviewPane({ api, trailing }: { readonly api: InspectorStoreApi; readonly trailing?: React.ReactNode | undefined }) {
387388
const files = useInspector(api, (state) => state.review.files);
388389
const selectedPath = useInspector(api, (state) => state.review.selectedPath);
389390
const view = useInspector(api, (state) => state.review.view);
@@ -407,6 +408,7 @@ export function ReviewPane({ api }: { readonly api: InspectorStoreApi }) {
407408
<PaneHeading
408409
family="review"
409410
summary={`${files.length} ${files.length === 1 ? "file" : "files"} · +${additions}${deletions} · ${viewedCount}/${files.length} viewed`}
411+
trailing={trailing}
410412
/>
411413
<div
412414
aria-label="Changed files"

apps/web/src/features/panes/TerminalsPane.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// into the bottom drawer, which is the only interactive terminal surface.
55
import { Badge, Button, cn } from "@t4-code/ui";
66
import { SquareTerminal } from "lucide-react";
7+
import type * as React from "react";
78

89
import { workspaceStore } from "../../state/store-instance.ts";
910
import {
@@ -69,9 +70,11 @@ function ShellRow({
6970
export function TerminalsPane({
7071
api,
7172
sessionId,
73+
trailing,
7274
}: {
7375
readonly api: InspectorStoreApi;
7476
readonly sessionId: string;
77+
readonly trailing?: React.ReactNode | undefined;
7578
}) {
7679
const agentShells = useInspector(api, (state) => state.terminals);
7780
const drawerApi = getTerminalStore(sessionId);
@@ -106,6 +109,7 @@ export function TerminalsPane({
106109
<PaneHeading
107110
family="terminals"
108111
summary={`${agentShells.length + userTabs.length} shells · ${runningCount} running`}
112+
trailing={trailing}
109113
/>
110114
<div className="min-h-0 flex-1 overflow-y-auto p-1.5">
111115
{agentShells.length > 0 && (

0 commit comments

Comments
 (0)