Refine Foundry UI layout and styling (#235)

* feat: modernize chat UI and rename handoff to task

- Remove agent message bubbles, keep user bubbles (right-aligned)
- Rename "Handoffs" to "Tasks" with ListChecks icon in sidebar
- Move model picker inside composer, add renderFooter to ChatComposer SDK
- Make project sections collapsible with hover-only chevrons
- Remove divider between chat and composer
- Update model picker chevron to flip on open/close
- Replace all user-visible "handoff" strings with "task" across frontend

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: real org mock data, model picker styling, project icons, task minutes indicator

- Replace fake acme/* mock data with real rivet-dev GitHub org repos and PRs
- Fix model picker popover: dark gray surface with backdrop blur instead of pure black
- Add colored letter icons to project section headers (swap to chevron on hover)
- Add "847 min used" indicator in transcript header
- Rename browser tab title from OpenHandoff to Foundry
- Reduce transcript header title font weight to 500

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: refine Foundry UI — single-line task cards, dark user bubbles, curved panel corners, send icon

- Collapse task sidebar cards to single-line layout (title, number, diffs, timestamp)
- Dark-themed user message bubbles matching site theme
- Curved top-left corner on center chat panel with border line
- Subtle focus border on composer input
- Replace ArrowUpFromLine with SendHorizonal icon
- Tab strip gaps, padding, and divider alignment fixes
- Plus button with visible background
- Right sidebar header color matching

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nicholas Kissel 2026-03-11 01:50:36 -07:00 committed by GitHub
parent 20082512a3
commit e792a720a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 201 additions and 123 deletions

View file

@ -4,15 +4,15 @@ export const appTheme: Theme = createDarkTheme({
colors: {
primary: "#e4e4e7", // zinc-200
accent: "#ff4f00", // orange accent (inspector)
backgroundPrimary: "#000000", // pure black (inspector --bg)
backgroundSecondary: "#0a0a0b", // near-black panels (inspector --bg-panel)
backgroundTertiary: "#0a0a0b", // same as panel (border provides separation)
backgroundPrimary: "#09090b", // darkest — chat center panel
backgroundSecondary: "#0f0f11", // slightly lighter — sidebars
backgroundTertiary: "#0c0c0e", // center + right panel headers
backgroundInversePrimary: "#fafafa",
contentPrimary: "#ffffff", // white (inspector --text)
contentSecondary: "#a1a1aa", // zinc-400 (inspector --muted)
contentTertiary: "#71717a", // zinc-500
contentInversePrimary: "#000000",
borderOpaque: "rgba(255, 255, 255, 0.18)", // inspector --border
borderTransparent: "rgba(255, 255, 255, 0.14)", // inspector --border-2
borderOpaque: "rgba(255, 255, 255, 0.10)", // inspector --border
borderTransparent: "rgba(255, 255, 255, 0.07)", // inspector --border-2
},
});

View file

@ -455,6 +455,7 @@ const TranscriptPanel = memo(function TranscriptPanel({
}
}}
/>
<div style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column", backgroundColor: "#09090b", borderTopLeftRadius: "12px", borderLeft: "1px solid rgba(255, 255, 255, 0.10)", borderTop: "1px solid rgba(255, 255, 255, 0.10)", overflow: "hidden" }}>
<TabStrip
handoff={handoff}
activeTabId={activeTabId}
@ -510,8 +511,8 @@ const TranscriptPanel = memo(function TranscriptPanel({
border: 0,
borderRadius: "999px",
padding: "10px 18px",
background: "#ff4f00",
color: "#fff",
background: "rgba(255, 255, 255, 0.12)",
color: "#e4e4e7",
cursor: "pointer",
fontWeight: 600,
}}
@ -554,6 +555,7 @@ const TranscriptPanel = memo(function TranscriptPanel({
onSetDefaultModel={setDefaultModel}
/>
) : null}
</div>
</SPanel>
);
});
@ -723,7 +725,23 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId }
handoffWorkbenchClient.getSnapshot.bind(handoffWorkbenchClient),
);
const handoffs = viewModel.handoffs ?? [];
const projects = viewModel.projects ?? [];
const rawProjects = viewModel.projects ?? [];
const [projectOrder, setProjectOrder] = useState<string[] | null>(null);
const projects = useMemo(() => {
if (!projectOrder) return rawProjects;
const byId = new Map(rawProjects.map((p) => [p.id, p]));
const ordered = projectOrder.map((id) => byId.get(id)).filter(Boolean) as typeof rawProjects;
for (const p of rawProjects) {
if (!projectOrder.includes(p.id)) ordered.push(p);
}
return ordered;
}, [rawProjects, projectOrder]);
const reorderProjects = useCallback((fromIndex: number, toIndex: number) => {
const ids = projects.map((p) => p.id);
const [moved] = ids.splice(fromIndex, 1);
ids.splice(toIndex, 0, moved!);
setProjectOrder(ids);
}, [projects]);
const [activeTabIdByHandoff, setActiveTabIdByHandoff] = useState<Record<string, string | null>>({});
const [lastAgentTabIdByHandoff, setLastAgentTabIdByHandoff] = useState<Record<string, string | null>>({});
const [openDiffsByHandoff, setOpenDiffsByHandoff] = useState<Record<string, string[]>>({});
@ -1027,34 +1045,35 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId }
>
<div
style={{
width: "min(520px, 100%)",
border: "1px solid rgba(255, 255, 255, 0.14)",
borderRadius: "18px",
background: "#111113",
boxShadow: "0 32px 80px rgba(0, 0, 0, 0.45)",
padding: "24px",
width: "min(440px, 100%)",
border: "1px solid rgba(255, 255, 255, 0.10)",
borderRadius: "12px",
background: "rgba(24, 24, 27, 0.98)",
backdropFilter: "blur(16px)",
boxShadow: "0 24px 64px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.04)",
padding: "28px",
display: "flex",
flexDirection: "column",
gap: "16px",
gap: "20px",
}}
>
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
<div style={{ fontSize: "12px", letterSpacing: "0.08em", textTransform: "uppercase", color: "rgba(255, 255, 255, 0.5)" }}>Onboarding</div>
<h2 style={{ margin: 0, fontSize: "24px", lineHeight: 1.1 }}>Give us support for sandbox agent</h2>
<p style={{ margin: 0, color: "rgba(255, 255, 255, 0.72)", lineHeight: 1.5 }}>
Before you keep going, give us support for sandbox agent and star the repo right here in the app.
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
<div style={{ fontSize: "11px", letterSpacing: "0.06em", textTransform: "uppercase", fontWeight: 600, color: "rgba(255, 255, 255, 0.4)" }}>Welcome to Foundry</div>
<h2 style={{ margin: 0, fontSize: "18px", fontWeight: 500, lineHeight: 1.3 }}>Support Sandbox Agent</h2>
<p style={{ margin: 0, color: "rgba(255, 255, 255, 0.55)", fontSize: "13px", lineHeight: 1.6 }}>
Star the repo to help us grow and stay up to date with new releases.
</p>
</div>
{starRepoError ? (
<div
style={{
borderRadius: "12px",
border: "1px solid rgba(255, 110, 110, 0.32)",
background: "rgba(255, 110, 110, 0.08)",
padding: "12px 14px",
color: "#ffb4b4",
fontSize: "13px",
borderRadius: "8px",
border: "1px solid rgba(255, 110, 110, 0.24)",
background: "rgba(255, 110, 110, 0.06)",
padding: "10px 12px",
color: "#ff9b9b",
fontSize: "12px",
}}
data-testid="onboarding-star-repo-error"
>
@ -1062,18 +1081,20 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId }
</div>
) : null}
<div style={{ display: "flex", justifyContent: "flex-end", gap: "10px" }}>
<div style={{ display: "flex", justifyContent: "flex-end", gap: "8px" }}>
<button
type="button"
onClick={dismissStarRepoPrompt}
style={{
border: "1px solid rgba(255, 255, 255, 0.14)",
borderRadius: "999px",
padding: "10px 16px",
background: "transparent",
color: "#e4e4e7",
border: "1px solid rgba(255, 255, 255, 0.10)",
borderRadius: "6px",
padding: "8px 14px",
background: "rgba(255, 255, 255, 0.05)",
color: "rgba(255, 255, 255, 0.7)",
cursor: "pointer",
fontWeight: 600,
fontSize: "12px",
fontWeight: 500,
transition: "all 160ms ease",
}}
>
Maybe later
@ -1084,16 +1105,18 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId }
disabled={starRepoPending}
style={{
border: 0,
borderRadius: "999px",
padding: "10px 16px",
background: starRepoPending ? "#7f5539" : "#ff4f00",
color: "#fff",
borderRadius: "6px",
padding: "8px 14px",
background: starRepoPending ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.12)",
color: "#e4e4e7",
cursor: starRepoPending ? "progress" : "pointer",
fontWeight: 700,
fontSize: "12px",
fontWeight: 600,
transition: "all 160ms ease",
}}
data-testid="onboarding-star-repo-submit"
>
{starRepoPending ? "Starring..." : "Star the sandbox agent repo"}
{starRepoPending ? "Starring..." : "Star the repo"}
</button>
</div>
</div>
@ -1112,8 +1135,9 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId }
onMarkUnread={markHandoffUnread}
onRenameHandoff={renameHandoff}
onRenameBranch={renameBranch}
onReorderProjects={reorderProjects}
/>
<SPanel>
<SPanel $style={{ backgroundColor: "#09090b" }}>
<ScrollBody>
<div
style={{
@ -1148,8 +1172,8 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId }
border: 0,
borderRadius: "999px",
padding: "10px 18px",
background: viewModel.repos.length > 0 ? "#ff4f00" : "#444",
color: "#fff",
background: viewModel.repos.length > 0 ? "rgba(255, 255, 255, 0.12)" : "#444",
color: "#e4e4e7",
cursor: viewModel.repos.length > 0 ? "pointer" : "not-allowed",
fontWeight: 600,
}}
@ -1178,6 +1202,7 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId }
onMarkUnread={markHandoffUnread}
onRenameHandoff={renameHandoff}
onRenameBranch={renameBranch}
onReorderProjects={reorderProjects}
/>
<TranscriptPanel
handoff={activeHandoff}

View file

@ -49,8 +49,8 @@ const TranscriptMessageBody = memo(function TranscriptMessageBody({
borderTopRightRadius: "16px",
...(isUser
? {
backgroundColor: "#ffffff",
color: "#000000",
backgroundColor: "rgba(255, 255, 255, 0.10)",
color: "#e4e4e7",
borderBottomLeftRadius: "16px",
borderBottomRightRadius: "4px",
}
@ -107,7 +107,7 @@ const TranscriptMessageBody = memo(function TranscriptMessageBody({
})}
>
<Copy size={11} />
{isCopied ? "Copied" : "Copy"}
{isCopied ? "Copied" : null}
</button>
</div>
</div>

View file

@ -1,7 +1,7 @@
import { memo, type Ref } from "react";
import { useStyletron } from "baseui";
import { ChatComposer, type ChatComposerClassNames } from "@sandbox-agent/react";
import { ArrowUpFromLine, FileCode, Square, X } from "lucide-react";
import { FileCode, SendHorizonal, Square, X } from "lucide-react";
import { ModelPicker } from "./model-picker";
import { PROMPT_TEXTAREA_MAX_HEIGHT, PROMPT_TEXTAREA_MIN_HEIGHT } from "./ui";
@ -45,7 +45,7 @@ export const PromptComposer = memo(function PromptComposer({
borderRadius: "16px",
minHeight: `${PROMPT_TEXTAREA_MIN_HEIGHT + 36}px`,
transition: "border-color 200ms ease",
":focus-within": { borderColor: "rgba(255, 255, 255, 0.3)" },
":focus-within": { borderColor: "rgba(255, 255, 255, 0.15)" },
display: "flex",
flexDirection: "column",
}),
@ -82,9 +82,9 @@ export const PromptComposer = memo(function PromptComposer({
justifyContent: "center",
color: theme.colors.contentPrimary,
transition: "background 200ms ease",
backgroundColor: isRunning ? "rgba(255, 255, 255, 0.06)" : "#ff4f00",
backgroundColor: isRunning ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.12)",
":hover": {
backgroundColor: isRunning ? "rgba(255, 255, 255, 0.12)" : "#ff6a00",
backgroundColor: isRunning ? "rgba(255, 255, 255, 0.12)" : "rgba(255, 255, 255, 0.20)",
},
":disabled": {
cursor: "not-allowed",
@ -157,7 +157,7 @@ export const PromptComposer = memo(function PromptComposer({
allowEmptySubmit={isRunning}
submitLabel={isRunning ? "Stop" : "Send"}
classNames={composerClassNames}
renderSubmitContent={() => (isRunning ? <Square size={16} /> : <ArrowUpFromLine size={16} />)}
renderSubmitContent={() => (isRunning ? <Square size={16} /> : <SendHorizonal size={16} />)}
renderFooter={() => (
<div className={css({ padding: "0 10px 8px" })}>
<ModelPicker

View file

@ -135,8 +135,8 @@ export const RightSidebar = memo(function RightSidebar({
);
return (
<SPanel>
<PanelHeaderBar>
<SPanel $style={{ backgroundColor: "#09090b" }}>
<PanelHeaderBar $style={{ backgroundColor: "#0f0f11", borderBottom: "none" }}>
<div className={css({ flex: 1 })} />
{!isTerminal ? (
<div className={css({ display: "flex", alignItems: "center", gap: "4px" })}>
@ -208,12 +208,14 @@ export const RightSidebar = memo(function RightSidebar({
) : null}
</PanelHeaderBar>
<div style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column", borderTop: `1px solid rgba(255, 255, 255, 0.10)` }}>
<div
className={css({
display: "flex",
alignItems: "stretch",
gap: "4px",
borderBottom: `1px solid ${theme.colors.borderOpaque}`,
backgroundColor: theme.colors.backgroundSecondary,
backgroundColor: "#09090b",
height: "41px",
minHeight: "41px",
flexShrink: 0,
@ -226,19 +228,21 @@ export const RightSidebar = memo(function RightSidebar({
display: "flex",
alignItems: "center",
gap: "6px",
height: "100%",
padding: "0 16px",
padding: "4px 12px",
marginTop: "6px",
marginBottom: "6px",
marginLeft: "6px",
borderRadius: "8px",
cursor: "pointer",
fontSize: "12px",
fontWeight: 600,
fontWeight: 500,
whiteSpace: "nowrap",
color: rightTab === "changes" ? theme.colors.contentPrimary : theme.colors.contentSecondary,
borderBottom: `2px solid ${rightTab === "changes" ? "#ff4f00" : "transparent"}`,
marginBottom: "-1px",
transitionProperty: "color, border-color",
backgroundColor: rightTab === "changes" ? "rgba(255, 255, 255, 0.06)" : "transparent",
transitionProperty: "color, background-color",
transitionDuration: "200ms",
transitionTimingFunction: "ease",
":hover": { color: "#e4e4e7" },
":hover": { color: "#e4e4e7", backgroundColor: rightTab === "changes" ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.04)" },
})}
>
Changes
@ -268,19 +272,20 @@ export const RightSidebar = memo(function RightSidebar({
all: "unset",
display: "flex",
alignItems: "center",
height: "100%",
padding: "0 16px",
padding: "4px 12px",
marginTop: "6px",
marginBottom: "6px",
borderRadius: "8px",
cursor: "pointer",
fontSize: "12px",
fontWeight: 600,
fontWeight: 500,
whiteSpace: "nowrap",
color: rightTab === "files" ? theme.colors.contentPrimary : theme.colors.contentSecondary,
borderBottom: `2px solid ${rightTab === "files" ? "#ff4f00" : "transparent"}`,
marginBottom: "-1px",
transitionProperty: "color, border-color",
backgroundColor: rightTab === "files" ? "rgba(255, 255, 255, 0.06)" : "transparent",
transitionProperty: "color, background-color",
transitionDuration: "200ms",
transitionTimingFunction: "ease",
":hover": { color: "#e4e4e7" },
":hover": { color: "#e4e4e7", backgroundColor: rightTab === "files" ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.04)" },
})}
>
All Files
@ -360,6 +365,7 @@ export const RightSidebar = memo(function RightSidebar({
</div>
)}
</ScrollBody>
</div>
{contextMenu.menu ? <ContextMenuOverlay menu={contextMenu.menu} onClose={contextMenu.close} /> : null}
</SPanel>
);

View file

@ -1,4 +1,4 @@
import { memo, useState } from "react";
import { memo, useRef, useState } from "react";
import { useStyletron } from "baseui";
import { LabelSmall, LabelXSmall } from "baseui/typography";
import { ChevronDown, ChevronUp, CloudUpload, GitPullRequestDraft, ListChecks, Plus } from "lucide-react";
@ -30,6 +30,7 @@ export const Sidebar = memo(function Sidebar({
onMarkUnread,
onRenameHandoff,
onRenameBranch,
onReorderProjects,
}: {
projects: ProjectSection[];
activeId: string;
@ -38,10 +39,13 @@ export const Sidebar = memo(function Sidebar({
onMarkUnread: (id: string) => void;
onRenameHandoff: (id: string) => void;
onRenameBranch: (id: string) => void;
onReorderProjects: (fromIndex: number, toIndex: number) => void;
}) {
const [css, theme] = useStyletron();
const contextMenu = useContextMenu();
const [collapsedProjects, setCollapsedProjects] = useState<Record<string, boolean>>({});
const dragIndexRef = useRef<number | null>(null);
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
return (
<SPanel>
@ -53,10 +57,10 @@ export const Sidebar = memo(function Sidebar({
display: none !important;
}
`}</style>
<PanelHeaderBar>
<PanelHeaderBar $style={{ backgroundColor: "transparent", borderBottom: "none" }}>
<LabelSmall
color={theme.colors.contentPrimary}
$style={{ fontWeight: 600, flex: 1, fontSize: "13px", display: "flex", alignItems: "center", gap: "6px" }}
$style={{ fontWeight: 500, flex: 1, fontSize: "13px", display: "flex", alignItems: "center", gap: "6px" }}
>
<ListChecks size={14} />
Tasks
@ -65,17 +69,17 @@ export const Sidebar = memo(function Sidebar({
onClick={onCreate}
className={css({
all: "unset",
width: "24px",
height: "24px",
borderRadius: "4px",
backgroundColor: "#ff4f00",
color: "#ffffff",
width: "26px",
height: "26px",
borderRadius: "8px",
backgroundColor: "rgba(255, 255, 255, 0.12)",
color: "#e4e4e7",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
transition: "background 200ms ease",
":hover": { backgroundColor: "#ff6a00" },
":hover": { backgroundColor: "rgba(255, 255, 255, 0.20)" },
})}
>
<Plus size={14} />
@ -83,11 +87,48 @@ export const Sidebar = memo(function Sidebar({
</PanelHeaderBar>
<ScrollBody>
<div className={css({ padding: "8px", display: "flex", flexDirection: "column", gap: "4px" })}>
{projects.map((project) => {
{projects.map((project, projectIndex) => {
const isCollapsed = collapsedProjects[project.id] === true;
const isDragOver = dragOverIndex === projectIndex && dragIndexRef.current !== projectIndex;
return (
<div key={project.id} className={css({ display: "flex", flexDirection: "column", gap: "4px" })}>
<div
key={project.id}
draggable
onDragStart={(event) => {
dragIndexRef.current = projectIndex;
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/plain", String(projectIndex));
}}
onDragOver={(event) => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
setDragOverIndex(projectIndex);
}}
onDragLeave={() => {
setDragOverIndex((current) => (current === projectIndex ? null : current));
}}
onDrop={(event) => {
event.preventDefault();
const fromIndex = dragIndexRef.current;
if (fromIndex != null && fromIndex !== projectIndex) {
onReorderProjects(fromIndex, projectIndex);
}
dragIndexRef.current = null;
setDragOverIndex(null);
}}
onDragEnd={() => {
dragIndexRef.current = null;
setDragOverIndex(null);
}}
className={css({
display: "flex",
flexDirection: "column",
gap: "4px",
borderTop: isDragOver ? "2px solid #ff4f00" : "2px solid transparent",
transition: "border-color 150ms ease",
})}
>
<div
onClick={() =>
setCollapsedProjects((current) => ({
@ -102,7 +143,7 @@ export const Sidebar = memo(function Sidebar({
justifyContent: "space-between",
padding: "10px 8px 4px",
gap: "8px",
cursor: "pointer",
cursor: "grab",
userSelect: "none",
":hover": { opacity: 0.8 },
})}
@ -150,9 +191,11 @@ export const Sidebar = memo(function Sidebar({
{project.label}
</LabelSmall>
</div>
<LabelXSmall color={theme.colors.contentTertiary}>
{formatRelativeAge(project.updatedAtMs)}
</LabelXSmall>
{isCollapsed ? (
<LabelXSmall color={theme.colors.contentTertiary}>
{formatRelativeAge(project.updatedAtMs)}
</LabelXSmall>
) : null}
</div>
{!isCollapsed && project.handoffs.map((handoff) => {
@ -177,7 +220,7 @@ export const Sidebar = memo(function Sidebar({
])
}
className={css({
padding: "12px",
padding: "8px 12px",
borderRadius: "8px",
border: "1px solid transparent",
backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "transparent",
@ -204,35 +247,17 @@ export const Sidebar = memo(function Sidebar({
</div>
<LabelSmall
$style={{
fontWeight: 600,
flex: 1,
fontWeight: hasUnread ? 600 : 400,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
minWidth: 0,
flexShrink: 1,
}}
color={isDim ? theme.colors.contentSecondary : theme.colors.contentPrimary}
color={hasUnread ? "#ffffff" : theme.colors.contentSecondary}
>
{handoff.title}
</LabelSmall>
{hasDiffs ? (
<div className={css({ display: "flex", gap: "4px", flexShrink: 0 })}>
<span className={css({ fontSize: "11px", color: "#7ee787" })}>+{totalAdded}</span>
<span className={css({ fontSize: "11px", color: "#ffa198" })}>-{totalRemoved}</span>
</div>
) : null}
</div>
<div className={css({ display: "flex", alignItems: "center", marginTop: "4px", gap: "6px" })}>
<LabelXSmall
color={theme.colors.contentTertiary}
$style={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
flexShrink: 1,
}}
>
{handoff.repoName}
</LabelXSmall>
{handoff.pullRequest != null ? (
<span className={css({ display: "inline-flex", alignItems: "center", gap: "4px", flexShrink: 0 })}>
<LabelXSmall color={theme.colors.contentSecondary} $style={{ fontWeight: 600 }}>
@ -243,7 +268,13 @@ export const Sidebar = memo(function Sidebar({
) : (
<GitPullRequestDraft size={11} color={theme.colors.contentTertiary} />
)}
<LabelXSmall color={theme.colors.contentTertiary} $style={{ marginLeft: "auto", flexShrink: 0 }}>
{hasDiffs ? (
<div className={css({ display: "flex", gap: "4px", flexShrink: 0, marginLeft: "auto" })}>
<span className={css({ fontSize: "11px", color: "#7ee787" })}>+{totalAdded}</span>
<span className={css({ fontSize: "11px", color: "#ffa198" })}>-{totalRemoved}</span>
</div>
) : null}
<LabelXSmall color={theme.colors.contentTertiary} $style={{ flexShrink: 0, marginLeft: hasDiffs ? undefined : "auto" }}>
{formatRelativeAge(handoff.updatedAtMs)}
</LabelXSmall>
</div>

View file

@ -42,12 +42,18 @@ export const TabStrip = memo(function TabStrip({
return (
<>
<style>{`
[data-tab]:hover [data-tab-close] { opacity: 0.5 !important; }
[data-tab]:hover [data-tab-close]:hover { opacity: 1 !important; }
`}</style>
<div
className={css({
display: "flex",
alignItems: "stretch",
borderBottom: `1px solid ${theme.colors.borderOpaque}`,
backgroundColor: theme.colors.backgroundSecondary,
gap: "4px",
backgroundColor: "#09090b",
paddingLeft: "6px",
height: "41px",
minHeight: "41px",
overflowX: "auto",
@ -79,16 +85,20 @@ export const TabStrip = memo(function TabStrip({
...(handoff.tabs.length > 1 ? [{ label: "Close tab", onClick: () => onCloseTab(tab.id) }] : []),
])
}
data-tab
className={css({
display: "flex",
alignItems: "center",
gap: "6px",
padding: "0 14px",
borderBottom: isActive ? "2px solid #ff4f00" : "2px solid transparent",
padding: "4px 12px",
marginTop: "6px",
marginBottom: "6px",
borderRadius: "8px",
backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "transparent",
cursor: "pointer",
transition: "color 200ms ease, border-color 200ms ease",
transition: "color 200ms ease, background-color 200ms ease",
flexShrink: 0,
":hover": { color: "#e4e4e7" },
":hover": { color: "#e4e4e7", backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.04)" },
})}
>
<div
@ -130,7 +140,7 @@ export const TabStrip = memo(function TabStrip({
})}
/>
) : (
<LabelXSmall color={isActive ? theme.colors.contentPrimary : theme.colors.contentSecondary} $style={{ fontWeight: 600 }}>
<LabelXSmall color={isActive ? theme.colors.contentPrimary : theme.colors.contentSecondary} $style={{ fontWeight: 500 }}>
{tab.sessionName}
</LabelXSmall>
)}
@ -138,7 +148,8 @@ export const TabStrip = memo(function TabStrip({
<X
size={11}
color={theme.colors.contentTertiary}
className={css({ cursor: "pointer", opacity: 0.5, ":hover": { opacity: 1 } })}
data-tab-close
className={css({ cursor: "pointer", opacity: 0 })}
onClick={(event) => {
event.stopPropagation();
onCloseTab(tab.id);
@ -161,29 +172,34 @@ export const TabStrip = memo(function TabStrip({
onCloseDiffTab(path);
}
}}
data-tab
className={css({
display: "flex",
alignItems: "center",
gap: "6px",
padding: "0 14px",
borderBottom: "2px solid transparent",
padding: "4px 12px",
marginTop: "6px",
marginBottom: "6px",
borderRadius: "8px",
backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "transparent",
cursor: "pointer",
transition: "color 200ms ease, border-color 200ms ease",
transition: "color 200ms ease, background-color 200ms ease",
flexShrink: 0,
":hover": { color: "#e4e4e7" },
":hover": { color: "#e4e4e7", backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.04)" },
})}
>
<FileCode size={12} color={isActive ? theme.colors.contentPrimary : theme.colors.contentSecondary} />
<LabelXSmall
color={isActive ? theme.colors.contentPrimary : theme.colors.contentSecondary}
$style={{ fontWeight: 600, fontFamily: '"IBM Plex Mono", monospace' }}
$style={{ fontWeight: 500, fontFamily: '"IBM Plex Mono", monospace' }}
>
{fileName(path)}
</LabelXSmall>
<X
size={11}
color={theme.colors.contentTertiary}
className={css({ cursor: "pointer", opacity: 0.5, ":hover": { opacity: 1 } })}
data-tab-close
className={css({ cursor: "pointer", opacity: 0 })}
onClick={(event) => {
event.stopPropagation();
onCloseDiffTab(path);

View file

@ -30,7 +30,7 @@ export const TranscriptHeader = memo(function TranscriptHeader({
const [css, theme] = useStyletron();
return (
<PanelHeaderBar>
<PanelHeaderBar $style={{ backgroundColor: "#0f0f11", borderBottom: "none" }}>
{editingField === "title" ? (
<input
autoFocus
@ -58,7 +58,7 @@ export const TranscriptHeader = memo(function TranscriptHeader({
<LabelSmall
title="Rename"
color={theme.colors.contentPrimary}
$style={{ fontWeight: 500, whiteSpace: "nowrap", cursor: "pointer", ":hover": { textDecoration: "underline" } }}
$style={{ fontWeight: 400, whiteSpace: "nowrap", cursor: "pointer", ":hover": { textDecoration: "underline" } }}
onClick={() => onStartEditingField("title", handoff.title)}
>
{handoff.title}

View file

@ -178,8 +178,8 @@ export const Shell = styled("div", ({ $theme }) => ({
display: "grid",
gap: "1px",
height: "100dvh",
backgroundColor: $theme.colors.borderOpaque,
gridTemplateColumns: "280px minmax(0, 1fr) 380px",
backgroundColor: $theme.colors.backgroundSecondary,
gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1.5fr) 380px",
overflow: "hidden",
}));