Standardize Foundry frontend colors with semantic design tokens (#241)

Extract hardcoded colors from 15+ component files into a centralized
token system (tokens.ts + shared-styles.ts) so all UI colors flow
through FoundryTokens. This eliminates 160+ scattered color values
and makes light mode a single-file change in the future.

- Add FoundryTokens interface with dark/light variants
- Add shared style helpers (buttons, cards, inputs, badges)
- Bridge CSS custom properties for styles.css theme support
- Add useFoundryTokens() hook and ColorMode context
- Migrate all mock-layout/* and mock-onboarding components

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nicholas Kissel 2026-03-11 20:52:06 -07:00 committed by GitHub
parent ed6e6f6fa5
commit f09b9090bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 887 additions and 523 deletions

View file

@ -2,10 +2,12 @@ import { memo, useEffect, useState } from "react";
import { useStyletron } from "baseui";
import { LabelXSmall } from "baseui/typography";
import { useFoundryTokens } from "../../app/theme";
import { formatMessageTimestamp, type HistoryEvent } from "./view-model";
export const HistoryMinimap = memo(function HistoryMinimap({ events, onSelect }: { events: HistoryEvent[]; onSelect: (event: HistoryEvent) => void }) {
const [css, theme] = useStyletron();
const [css] = useStyletron();
const t = useFoundryTokens();
const [open, setOpen] = useState(false);
const [activeEventId, setActiveEventId] = useState<string | null>(events[events.length - 1]?.id ?? null);
@ -42,10 +44,10 @@ export const HistoryMinimap = memo(function HistoryMinimap({ events, onSelect }:
})}
>
<div className={css({ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "6px" })}>
<LabelXSmall color={theme.colors.contentTertiary} $style={{ letterSpacing: "0.08em", textTransform: "uppercase" }}>
<LabelXSmall color={t.textTertiary} $style={{ letterSpacing: "0.08em", textTransform: "uppercase" }}>
Task Events
</LabelXSmall>
<LabelXSmall color={theme.colors.contentTertiary}>{events.length}</LabelXSmall>
<LabelXSmall color={t.textTertiary}>{events.length}</LabelXSmall>
</div>
<div className={css({ display: "flex", flexDirection: "column", gap: "6px" })}>
{events.map((event) => {
@ -70,12 +72,12 @@ export const HistoryMinimap = memo(function HistoryMinimap({ events, onSelect }:
padding: "9px 10px",
borderRadius: "12px",
cursor: "pointer",
backgroundColor: isActive ? "rgba(255, 255, 255, 0.08)" : "transparent",
color: isActive ? theme.colors.contentPrimary : theme.colors.contentSecondary,
backgroundColor: isActive ? t.borderSubtle : "transparent",
color: isActive ? t.textPrimary : t.textSecondary,
transition: "background 160ms ease, color 160ms ease",
":hover": {
backgroundColor: "rgba(255, 255, 255, 0.08)",
color: theme.colors.contentPrimary,
backgroundColor: t.borderSubtle,
color: t.textPrimary,
},
})}
>
@ -91,9 +93,9 @@ export const HistoryMinimap = memo(function HistoryMinimap({ events, onSelect }:
>
{event.preview}
</div>
<LabelXSmall color={theme.colors.contentTertiary}>{event.sessionName}</LabelXSmall>
<LabelXSmall color={t.textTertiary}>{event.sessionName}</LabelXSmall>
</div>
<LabelXSmall color={theme.colors.contentTertiary}>{formatMessageTimestamp(event.createdAtMs)}</LabelXSmall>
<LabelXSmall color={t.textTertiary}>{formatMessageTimestamp(event.createdAtMs)}</LabelXSmall>
</button>
);
})}
@ -119,7 +121,7 @@ export const HistoryMinimap = memo(function HistoryMinimap({ events, onSelect }:
className={css({
height: "3px",
borderRadius: "999px",
backgroundColor: isActive ? "#ff4f00" : "rgba(255, 255, 255, 0.22)",
backgroundColor: isActive ? t.accent : t.textMuted,
opacity: isActive ? 1 : 0.75,
transition: "background 160ms ease, opacity 160ms ease",
})}

View file

@ -4,6 +4,7 @@ import { useStyletron } from "baseui";
import { LabelSmall, LabelXSmall } from "baseui/typography";
import { Copy } from "lucide-react";
import { useFoundryTokens } from "../../app/theme";
import { HistoryMinimap } from "./history-minimap";
import { SpinnerDot } from "./ui";
import { buildDisplayMessages, formatMessageDuration, formatMessageTimestamp, type AgentTab, type HistoryEvent, type Message } from "./view-model";
@ -19,7 +20,8 @@ const TranscriptMessageBody = memo(function TranscriptMessageBody({
copiedMessageId: string | null;
onCopyMessage: (message: Message) => void;
}) {
const [css, theme] = useStyletron();
const [css] = useStyletron();
const t = useFoundryTokens();
const isUser = message.sender === "client";
const isCopied = copiedMessageId === message.id;
const messageTimestamp = formatMessageTimestamp(message.createdAtMs);
@ -47,8 +49,8 @@ const TranscriptMessageBody = memo(function TranscriptMessageBody({
...(isUser
? {
padding: "12px 16px",
backgroundColor: "rgba(255, 255, 255, 0.10)",
color: "#e4e4e7",
backgroundColor: t.borderDefault,
color: t.textPrimary,
borderTopLeftRadius: "18px",
borderTopRightRadius: "18px",
borderBottomLeftRadius: "18px",
@ -57,7 +59,7 @@ const TranscriptMessageBody = memo(function TranscriptMessageBody({
: {
backgroundColor: "transparent",
border: "none",
color: "#e4e4e7",
color: t.textPrimary,
borderRadius: "0",
padding: "0",
}),
@ -86,7 +88,7 @@ const TranscriptMessageBody = memo(function TranscriptMessageBody({
})}
>
{displayFooter ? (
<LabelXSmall color={theme.colors.contentTertiary} $style={{ fontFamily: '"IBM Plex Mono", monospace', letterSpacing: "0.01em" }}>
<LabelXSmall color={t.textTertiary} $style={{ fontFamily: '"IBM Plex Mono", monospace', letterSpacing: "0.01em" }}>
{displayFooter}
</LabelXSmall>
) : null}
@ -106,9 +108,9 @@ const TranscriptMessageBody = memo(function TranscriptMessageBody({
gap: "5px",
fontSize: "11px",
cursor: "pointer",
color: isCopied ? theme.colors.contentPrimary : theme.colors.contentSecondary,
color: isCopied ? t.textPrimary : t.textSecondary,
transition: "color 160ms ease",
":hover": { color: theme.colors.contentPrimary },
":hover": { color: t.textPrimary },
})}
>
<Copy size={11} />
@ -138,7 +140,8 @@ export const MessageList = memo(function MessageList({
onCopyMessage: (message: Message) => void;
thinkingTimerLabel: string | null;
}) {
const [css, theme] = useStyletron();
const [css] = useStyletron();
const t = useFoundryTokens();
const messages = useMemo(() => buildDisplayMessages(tab), [tab]);
const messagesById = useMemo(() => new Map(messages.map((message) => [message.id, message])), [messages]);
const transcriptEntries = useMemo<TranscriptEntry[]>(
@ -183,7 +186,7 @@ export const MessageList = memo(function MessageList({
display: "flex",
alignItems: "center",
gap: "8px",
color: "#ff4f00",
color: t.accent,
fontSize: "11px",
fontFamily: '"IBM Plex Mono", monospace',
letterSpacing: "0.01em",
@ -221,7 +224,7 @@ export const MessageList = memo(function MessageList({
gap: "8px",
})}
>
<LabelSmall color={theme.colors.contentTertiary}>
<LabelSmall color={t.textTertiary}>
{!tab.created ? "Choose an agent and model, then send your first message" : "No messages yet in this session"}
</LabelSmall>
</div>
@ -241,15 +244,15 @@ export const MessageList = memo(function MessageList({
renderThinkingState={() => (
<div className={transcriptClassNames.thinkingRow}>
<SpinnerDot size={12} />
<LabelXSmall color="#ff4f00" $style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<LabelXSmall color={t.accent} $style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<span>Agent is thinking</span>
{thinkingTimerLabel ? (
<span
className={css({
padding: "2px 7px",
borderRadius: "999px",
backgroundColor: "rgba(255, 79, 0, 0.12)",
border: "1px solid rgba(255, 79, 0, 0.2)",
backgroundColor: t.accentSubtle,
border: `1px solid rgba(255, 79, 0, 0.2)`,
fontFamily: '"IBM Plex Mono", monospace',
fontSize: "10px",
letterSpacing: "0.04em",

View file

@ -3,6 +3,7 @@ import { useStyletron } from "baseui";
import { StatefulPopover, PLACEMENT } from "baseui/popover";
import { ChevronDown, ChevronUp, Star } from "lucide-react";
import { useFoundryTokens } from "../../app/theme";
import { AgentIcon } from "./ui";
import { MODEL_GROUPS, modelLabel, providerAgent, type ModelId } from "./view-model";
@ -19,7 +20,8 @@ const ModelPickerContent = memo(function ModelPickerContent({
onSetDefault: (id: ModelId) => void;
close: () => void;
}) {
const [css, theme] = useStyletron();
const [css] = useStyletron();
const t = useFoundryTokens();
const [hoveredId, setHoveredId] = useState<ModelId | null>(null);
return (
@ -31,7 +33,7 @@ const ModelPickerContent = memo(function ModelPickerContent({
padding: "6px 12px",
fontSize: "10px",
fontWeight: 700,
color: theme.colors.contentTertiary,
color: t.textTertiary,
textTransform: "uppercase",
letterSpacing: "0.05em",
})}
@ -61,21 +63,21 @@ const ModelPickerContent = memo(function ModelPickerContent({
cursor: "pointer",
fontSize: "12px",
fontWeight: isActive ? 600 : 400,
color: isActive ? theme.colors.contentPrimary : theme.colors.contentSecondary,
color: isActive ? t.textPrimary : t.textSecondary,
borderRadius: "6px",
marginLeft: "4px",
marginRight: "4px",
":hover": { backgroundColor: "rgba(255, 255, 255, 0.08)" },
":hover": { backgroundColor: t.borderSubtle },
})}
>
<AgentIcon agent={agent} size={12} />
<span className={css({ flex: 1 })}>{model.label}</span>
{isDefault ? <Star size={11} fill="#ff4f00" color="#ff4f00" /> : null}
{isDefault ? <Star size={11} fill={t.accent} color={t.accent} /> : null}
{!isDefault && isHovered ? (
<Star
size={11}
color={theme.colors.contentTertiary}
className={css({ cursor: "pointer", ":hover": { color: "#ff4f00" } })}
color={t.textTertiary}
className={css({ cursor: "pointer", ":hover": { color: t.accent } })}
onClick={(event) => {
event.stopPropagation();
onSetDefault(model.id);
@ -102,7 +104,8 @@ export const ModelPicker = memo(function ModelPicker({
onChange: (id: ModelId) => void;
onSetDefault: (id: ModelId) => void;
}) {
const [css, theme] = useStyletron();
const [css] = useStyletron();
const t = useFoundryTokens();
const [isOpen, setIsOpen] = useState(false);
return (
@ -121,8 +124,8 @@ export const ModelPicker = memo(function ModelPicker({
borderTopRightRadius: "10px",
borderBottomLeftRadius: "10px",
borderBottomRightRadius: "10px",
border: "1px solid rgba(255, 255, 255, 0.10)",
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.04)",
border: `1px solid ${t.borderDefault}`,
boxShadow: `0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px ${t.interactiveSubtle}`,
zIndex: 100,
},
},
@ -150,10 +153,10 @@ export const ModelPicker = memo(function ModelPicker({
borderRadius: "6px",
fontSize: "12px",
fontWeight: 500,
color: theme.colors.contentSecondary,
backgroundColor: "rgba(255, 255, 255, 0.10)",
border: "1px solid rgba(255, 255, 255, 0.14)",
":hover": { color: theme.colors.contentPrimary, backgroundColor: "rgba(255, 255, 255, 0.14)" },
color: t.textSecondary,
backgroundColor: t.borderDefault,
border: `1px solid ${t.borderMedium}`,
":hover": { color: t.textPrimary, backgroundColor: t.borderMedium },
})}
>
{modelLabel(value)}

View file

@ -3,6 +3,7 @@ import { useStyletron } from "baseui";
import { ChatComposer, type ChatComposerClassNames } from "@sandbox-agent/react";
import { FileCode, SendHorizonal, Square, X } from "lucide-react";
import { useFoundryTokens } from "../../app/theme";
import { ModelPicker } from "./model-picker";
import { PROMPT_TEXTAREA_MAX_HEIGHT, PROMPT_TEXTAREA_MIN_HEIGHT } from "./ui";
import { fileName, type LineAttachment, type ModelId } from "./view-model";
@ -36,16 +37,17 @@ export const PromptComposer = memo(function PromptComposer({
onChangeModel: (model: ModelId) => void;
onSetDefaultModel: (model: ModelId) => void;
}) {
const [css, theme] = useStyletron();
const [css] = useStyletron();
const t = useFoundryTokens();
const composerClassNames: Partial<ChatComposerClassNames> = {
form: css({
position: "relative",
backgroundColor: "rgba(255, 255, 255, 0.06)",
border: `1px solid ${theme.colors.borderOpaque}`,
backgroundColor: t.interactiveHover,
border: `1px solid ${t.borderDefault}`,
borderRadius: "12px",
minHeight: `${PROMPT_TEXTAREA_MIN_HEIGHT + 36}px`,
transition: "border-color 200ms ease",
":focus-within": { borderColor: "rgba(255, 255, 255, 0.15)" },
":focus-within": { borderColor: t.borderMedium },
display: "flex",
flexDirection: "column",
}),
@ -57,7 +59,7 @@ export const PromptComposer = memo(function PromptComposer({
background: "transparent",
border: "none",
borderRadius: "12px 12px 0 0",
color: theme.colors.contentPrimary,
color: t.textPrimary,
fontSize: "13px",
fontFamily: "inherit",
resize: "none",
@ -66,7 +68,7 @@ export const PromptComposer = memo(function PromptComposer({
maxHeight: `${PROMPT_TEXTAREA_MAX_HEIGHT + 40}px`,
boxSizing: "border-box",
overflowY: "hidden",
"::placeholder": { color: theme.colors.contentSecondary },
"::placeholder": { color: t.textSecondary },
}),
submit: css({
appearance: "none",
@ -87,11 +89,11 @@ export const PromptComposer = memo(function PromptComposer({
justifyContent: "center",
lineHeight: 0,
fontSize: 0,
color: theme.colors.contentPrimary,
color: t.textPrimary,
transition: "background 200ms ease",
backgroundColor: isRunning ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.12)",
backgroundColor: isRunning ? t.interactiveHover : t.borderMedium,
":hover": {
backgroundColor: isRunning ? "rgba(255, 255, 255, 0.12)" : "rgba(255, 255, 255, 0.20)",
backgroundColor: isRunning ? t.borderMedium : "rgba(255, 255, 255, 0.20)",
},
":disabled": {
cursor: "not-allowed",
@ -105,7 +107,7 @@ export const PromptComposer = memo(function PromptComposer({
width: "100%",
height: "100%",
lineHeight: 0,
color: isRunning ? theme.colors.contentPrimary : "#ffffff",
color: isRunning ? t.textPrimary : t.textPrimary,
}),
};
@ -131,11 +133,11 @@ export const PromptComposer = memo(function PromptComposer({
gap: "4px",
padding: "2px 8px",
borderRadius: "4px",
backgroundColor: "rgba(255, 255, 255, 0.06)",
border: "1px solid rgba(255, 255, 255, 0.14)",
backgroundColor: t.interactiveHover,
border: `1px solid ${t.borderMedium}`,
fontSize: "11px",
fontFamily: '"IBM Plex Mono", monospace',
color: theme.colors.contentSecondary,
color: t.textSecondary,
})}
>
<FileCode size={11} />

View file

@ -3,6 +3,7 @@ import { useStyletron } from "baseui";
import { LabelSmall } from "baseui/typography";
import { Archive, ArrowUpFromLine, ChevronRight, FileCode, FilePlus, FileX, FolderOpen, GitPullRequest, PanelRight } from "lucide-react";
import { useFoundryTokens } from "../../app/theme";
import { type ContextMenuItem, ContextMenuOverlay, PanelHeaderBar, SPanel, ScrollBody, useContextMenu } from "./ui";
import { type FileTreeNode, type Task, diffTabId } from "./view-model";
@ -19,7 +20,8 @@ const FileTree = memo(function FileTree({
onFileContextMenu: (event: MouseEvent, path: string) => void;
changedPaths: Set<string>;
}) {
const [css, theme] = useStyletron();
const [css] = useStyletron();
const t = useFoundryTokens();
const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
return (
@ -56,8 +58,8 @@ const FileTree = memo(function FileTree({
cursor: "pointer",
fontSize: "12px",
fontFamily: '"IBM Plex Mono", monospace',
color: isChanged ? theme.colors.contentPrimary : theme.colors.contentTertiary,
":hover": { backgroundColor: "rgba(255, 255, 255, 0.06)" },
color: isChanged ? t.textPrimary : t.textTertiary,
":hover": { backgroundColor: t.interactiveHover },
})}
>
{node.isDir ? (
@ -72,7 +74,7 @@ const FileTree = memo(function FileTree({
<FolderOpen size={13} />
</>
) : (
<FileCode size={13} color={isChanged ? theme.colors.contentPrimary : undefined} style={{ marginLeft: "16px" }} />
<FileCode size={13} color={isChanged ? t.textPrimary : undefined} style={{ marginLeft: "16px" }} />
)}
<span>{node.name}</span>
</div>
@ -103,7 +105,8 @@ export const RightSidebar = memo(function RightSidebar({
onPublishPr: () => void;
onToggleSidebar?: () => void;
}) {
const [css, theme] = useStyletron();
const [css] = useStyletron();
const t = useFoundryTokens();
const [rightTab, setRightTab] = useState<"changes" | "files">("changes");
const contextMenu = useContextMenu();
const changedPaths = useMemo(() => new Set(task.fileChanges.map((file) => file.path)), [task.fileChanges]);
@ -147,8 +150,8 @@ export const RightSidebar = memo(function RightSidebar({
);
return (
<SPanel $style={{ backgroundColor: "#09090b", minWidth: 0 }}>
<PanelHeaderBar $style={{ backgroundColor: "#0f0f11", borderBottom: "none", overflow: "hidden" }}>
<SPanel $style={{ backgroundColor: t.surfacePrimary, minWidth: 0 }}>
<PanelHeaderBar $style={{ backgroundColor: t.surfaceSecondary, borderBottom: "none", overflow: "hidden" }}>
<div ref={headerRef} className={css({ display: "flex", alignItems: "center", flex: 1, minWidth: 0, justifyContent: "flex-end", gap: "2px" })}>
{!isTerminal ? (
<div className={css({ display: "flex", alignItems: "center", gap: "2px", flexShrink: 1, minWidth: 0 })}>
@ -178,10 +181,10 @@ export const RightSidebar = memo(function RightSidebar({
lineHeight: 1,
whiteSpace: "nowrap",
flexShrink: 0,
color: theme.colors.contentSecondary,
color: t.textSecondary,
cursor: "pointer",
transition: "all 200ms ease",
":hover": { backgroundColor: "rgba(255, 255, 255, 0.06)", color: theme.colors.contentPrimary },
":hover": { backgroundColor: t.interactiveHover, color: t.textPrimary },
})}
>
<GitPullRequest size={12} style={{ flexShrink: 0 }} />
@ -205,10 +208,10 @@ export const RightSidebar = memo(function RightSidebar({
lineHeight: 1,
whiteSpace: "nowrap",
flexShrink: 0,
color: theme.colors.contentSecondary,
color: t.textSecondary,
cursor: "pointer",
transition: "all 200ms ease",
":hover": { backgroundColor: "rgba(255, 255, 255, 0.06)", color: theme.colors.contentPrimary },
":hover": { backgroundColor: t.interactiveHover, color: t.textPrimary },
})}
>
<ArrowUpFromLine size={12} style={{ flexShrink: 0 }} />
@ -233,10 +236,10 @@ export const RightSidebar = memo(function RightSidebar({
lineHeight: 1,
whiteSpace: "nowrap",
flexShrink: 0,
color: theme.colors.contentSecondary,
color: t.textSecondary,
cursor: "pointer",
transition: "all 200ms ease",
":hover": { backgroundColor: "rgba(255, 255, 255, 0.06)", color: theme.colors.contentPrimary },
":hover": { backgroundColor: t.interactiveHover, color: t.textPrimary },
})}
>
<Archive size={12} style={{ flexShrink: 0 }} />
@ -256,13 +259,13 @@ export const RightSidebar = memo(function RightSidebar({
width: "26px",
height: "26px",
borderRadius: "6px",
color: "#71717a",
color: t.textTertiary,
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
":hover": { color: "#a1a1aa", backgroundColor: "rgba(255, 255, 255, 0.06)" },
":hover": { color: t.textSecondary, backgroundColor: t.interactiveHover },
})}
>
<PanelRight size={14} />
@ -277,8 +280,8 @@ export const RightSidebar = memo(function RightSidebar({
minHeight: 0,
display: "flex",
flexDirection: "column",
borderTop: "1px solid rgba(255, 255, 255, 0.10)",
borderRight: "1px solid rgba(255, 255, 255, 0.10)",
borderTop: `1px solid ${t.borderDefault}`,
borderRight: `1px solid ${t.borderDefault}`,
borderTopRightRadius: "12px",
overflow: "hidden",
}}
@ -288,8 +291,8 @@ export const RightSidebar = memo(function RightSidebar({
display: "flex",
alignItems: "stretch",
gap: "4px",
borderBottom: `1px solid ${theme.colors.borderOpaque}`,
backgroundColor: "#09090b",
borderBottom: `1px solid ${t.borderDefault}`,
backgroundColor: t.surfacePrimary,
height: "41px",
minHeight: "41px",
flexShrink: 0,
@ -318,12 +321,12 @@ export const RightSidebar = memo(function RightSidebar({
fontWeight: 500,
lineHeight: 1,
whiteSpace: "nowrap",
color: rightTab === "changes" ? theme.colors.contentPrimary : theme.colors.contentSecondary,
backgroundColor: rightTab === "changes" ? "rgba(255, 255, 255, 0.06)" : "transparent",
color: rightTab === "changes" ? t.textPrimary : t.textSecondary,
backgroundColor: rightTab === "changes" ? t.interactiveHover : "transparent",
transitionProperty: "color, background-color",
transitionDuration: "200ms",
transitionTimingFunction: "ease",
":hover": { color: "#e4e4e7", backgroundColor: rightTab === "changes" ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.04)" },
":hover": { color: t.textPrimary, backgroundColor: rightTab === "changes" ? t.interactiveHover : t.interactiveSubtle },
})}
>
Changes
@ -336,8 +339,8 @@ export const RightSidebar = memo(function RightSidebar({
minWidth: "16px",
height: "16px",
padding: "0 5px",
background: "#3f3f46",
color: "#a1a1aa",
background: t.surfaceElevated,
color: t.textSecondary,
fontSize: "9px",
fontWeight: 700,
borderRadius: "8px",
@ -367,12 +370,12 @@ export const RightSidebar = memo(function RightSidebar({
fontWeight: 500,
lineHeight: 1,
whiteSpace: "nowrap",
color: rightTab === "files" ? theme.colors.contentPrimary : theme.colors.contentSecondary,
backgroundColor: rightTab === "files" ? "rgba(255, 255, 255, 0.06)" : "transparent",
color: rightTab === "files" ? t.textPrimary : t.textSecondary,
backgroundColor: rightTab === "files" ? t.interactiveHover : "transparent",
transitionProperty: "color, background-color",
transitionDuration: "200ms",
transitionTimingFunction: "ease",
":hover": { color: "#e4e4e7", backgroundColor: rightTab === "files" ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.04)" },
":hover": { color: t.textPrimary, backgroundColor: rightTab === "files" ? t.interactiveHover : t.interactiveSubtle },
})}
>
All Files
@ -384,13 +387,13 @@ export const RightSidebar = memo(function RightSidebar({
<div className={css({ padding: "10px 14px", display: "flex", flexDirection: "column", gap: "2px" })}>
{task.fileChanges.length === 0 ? (
<div className={css({ padding: "20px 0", textAlign: "center" })}>
<LabelSmall color={theme.colors.contentTertiary}>No changes yet</LabelSmall>
<LabelSmall color={t.textTertiary}>No changes yet</LabelSmall>
</div>
) : null}
{task.fileChanges.map((file) => {
const isActive = activeTabId === diffTabId(file.path);
const TypeIcon = file.type === "A" ? FilePlus : file.type === "D" ? FileX : FileCode;
const iconColor = file.type === "A" ? "#7ee787" : file.type === "D" ? "#ffa198" : theme.colors.contentTertiary;
const iconColor = file.type === "A" ? t.statusSuccess : file.type === "D" ? t.statusError : t.textTertiary;
return (
<div
key={file.path}
@ -402,9 +405,9 @@ export const RightSidebar = memo(function RightSidebar({
gap: "8px",
padding: "6px 10px",
borderRadius: "6px",
backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "transparent",
backgroundColor: isActive ? t.interactiveHover : "transparent",
cursor: "pointer",
":hover": { backgroundColor: "rgba(255, 255, 255, 0.06)" },
":hover": { backgroundColor: t.interactiveHover },
})}
>
<TypeIcon size={14} color={iconColor} style={{ flexShrink: 0 }} />
@ -414,7 +417,7 @@ export const RightSidebar = memo(function RightSidebar({
minWidth: 0,
fontFamily: '"IBM Plex Mono", monospace',
fontSize: "12px",
color: isActive ? theme.colors.contentPrimary : theme.colors.contentSecondary,
color: isActive ? t.textPrimary : t.textSecondary,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
@ -432,8 +435,8 @@ export const RightSidebar = memo(function RightSidebar({
fontFamily: '"IBM Plex Mono", monospace',
})}
>
<span className={css({ color: "#7ee787" })}>+{file.added}</span>
<span className={css({ color: "#ffa198" })}>-{file.removed}</span>
<span className={css({ color: t.statusSuccess })}>+{file.added}</span>
<span className={css({ color: t.statusError })}>-{file.removed}</span>
<span className={css({ color: iconColor, fontWeight: 600, width: "10px", textAlign: "center" })}>{file.type}</span>
</div>
</div>
@ -446,7 +449,7 @@ export const RightSidebar = memo(function RightSidebar({
<FileTree nodes={task.fileTree} depth={0} onSelectFile={onOpenDiff} onFileContextMenu={openFileMenu} changedPaths={changedPaths} />
) : (
<div className={css({ padding: "20px 0", textAlign: "center" })}>
<LabelSmall color={theme.colors.contentTertiary}>No files yet</LabelSmall>
<LabelSmall color={t.textTertiary}>No files yet</LabelSmall>
</div>
)}
</div>

View file

@ -21,6 +21,8 @@ import {
import { formatRelativeAge, type Task, type ProjectSection } from "./view-model";
import { ContextMenuOverlay, TaskIndicator, PanelHeaderBar, SPanel, ScrollBody, useContextMenu } from "./ui";
import { activeMockOrganization, eligibleOrganizations, useMockAppClient, useMockAppSnapshot } from "../../lib/mock-app";
import { useFoundryTokens } from "../../app/theme";
import type { FoundryTokens } from "../../styles/tokens";
const PROJECT_COLORS = ["#6366f1", "#f59e0b", "#10b981", "#ef4444", "#8b5cf6", "#ec4899", "#06b6d4", "#f97316"];
@ -65,7 +67,8 @@ export const Sidebar = memo(function Sidebar({
onReorderProjects: (fromIndex: number, toIndex: number) => void;
onToggleSidebar?: () => void;
}) {
const [css, theme] = useStyletron();
const [css] = useStyletron();
const t = useFoundryTokens();
const contextMenu = useContextMenu();
const [collapsedProjects, setCollapsedProjects] = useState<Record<string, boolean>>({});
const dragIndexRef = useRef<number | null>(null);
@ -106,13 +109,13 @@ export const Sidebar = memo(function Sidebar({
width: "26px",
height: "26px",
borderRadius: "6px",
color: "#71717a",
color: t.textTertiary,
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
":hover": { color: "#a1a1aa", backgroundColor: "rgba(255, 255, 255, 0.06)" },
":hover": { color: t.textSecondary, backgroundColor: t.interactiveHover },
})}
>
<PanelLeft size={14} />
@ -122,7 +125,7 @@ export const Sidebar = memo(function Sidebar({
) : null}
<PanelHeaderBar $style={{ backgroundColor: "transparent", borderBottom: "none" }}>
<LabelSmall
color={theme.colors.contentPrimary}
color={t.textPrimary}
$style={{ fontWeight: 500, flex: 1, fontSize: "13px", display: "flex", alignItems: "center", gap: "6px", lineHeight: 1 }}
>
<ListChecks size={14} />
@ -140,13 +143,13 @@ export const Sidebar = memo(function Sidebar({
width: "26px",
height: "26px",
borderRadius: "6px",
color: "#71717a",
color: t.textTertiary,
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
":hover": { color: "#a1a1aa", backgroundColor: "rgba(255, 255, 255, 0.06)" },
":hover": { color: t.textSecondary, backgroundColor: t.interactiveHover },
})}
>
<PanelLeft size={14} />
@ -172,8 +175,8 @@ export const Sidebar = memo(function Sidebar({
width: "26px",
height: "26px",
borderRadius: "8px",
backgroundColor: newTaskRepos.length > 0 ? "rgba(255, 255, 255, 0.12)" : "rgba(255, 255, 255, 0.06)",
color: "#e4e4e7",
backgroundColor: newTaskRepos.length > 0 ? t.borderMedium : t.interactiveHover,
color: t.textPrimary,
cursor: newTaskRepos.length > 0 ? "pointer" : "not-allowed",
display: "flex",
alignItems: "center",
@ -188,7 +191,7 @@ export const Sidebar = memo(function Sidebar({
</div>
</PanelHeaderBar>
<div className={css({ padding: "0 8px 8px", display: "flex", flexDirection: "column", gap: "6px" })}>
<LabelXSmall color={theme.colors.contentTertiary} $style={{ textTransform: "uppercase", letterSpacing: "0.04em" }}>
<LabelXSmall color={t.textTertiary} $style={{ textTransform: "uppercase", letterSpacing: "0.04em" }}>
Repo
</LabelXSmall>
<select
@ -200,9 +203,9 @@ export const Sidebar = memo(function Sidebar({
className={css({
width: "100%",
borderRadius: "8px",
border: "1px solid rgba(255, 255, 255, 0.10)",
backgroundColor: "rgba(255, 255, 255, 0.05)",
color: "#f4f4f5",
border: `1px solid ${t.borderDefault}`,
backgroundColor: t.interactiveHover,
color: t.textPrimary,
fontSize: "12px",
padding: "8px 10px",
outline: "none",
@ -258,7 +261,7 @@ export const Sidebar = memo(function Sidebar({
display: "flex",
flexDirection: "column",
gap: "4px",
borderTop: isDragOver ? "2px solid #ff4f00" : "2px solid transparent",
borderTop: isDragOver ? `2px solid ${t.accent}` : "2px solid transparent",
transition: "border-color 150ms ease",
})}
>
@ -294,7 +297,7 @@ export const Sidebar = memo(function Sidebar({
fontSize: "9px",
fontWeight: 700,
lineHeight: 1,
color: "#fff",
color: t.textOnAccent,
backgroundColor: projectIconColor(project.label),
})}
data-project-icon
@ -302,15 +305,11 @@ export const Sidebar = memo(function Sidebar({
{projectInitial(project.label)}
</span>
<span className={css({ position: "absolute", inset: 0, display: "none", alignItems: "center", justifyContent: "center" })} data-chevron>
{isCollapsed ? (
<ChevronDown size={12} color={theme.colors.contentTertiary} />
) : (
<ChevronUp size={12} color={theme.colors.contentTertiary} />
)}
{isCollapsed ? <ChevronDown size={12} color={t.textTertiary} /> : <ChevronUp size={12} color={t.textTertiary} />}
</span>
</div>
<LabelSmall
color={theme.colors.contentSecondary}
color={t.textSecondary}
$style={{
fontSize: "11px",
fontWeight: 700,
@ -324,7 +323,7 @@ export const Sidebar = memo(function Sidebar({
{project.label}
</LabelSmall>
</div>
{isCollapsed ? <LabelXSmall color={theme.colors.contentTertiary}>{formatRelativeAge(project.updatedAtMs)}</LabelXSmall> : null}
{isCollapsed ? <LabelXSmall color={t.textTertiary}>{formatRelativeAge(project.updatedAtMs)}</LabelXSmall> : null}
</div>
{!isCollapsed &&
@ -353,11 +352,11 @@ export const Sidebar = memo(function Sidebar({
padding: "8px 12px",
borderRadius: "8px",
border: "1px solid transparent",
backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "transparent",
backgroundColor: isActive ? t.interactiveHover : "transparent",
cursor: "pointer",
transition: "all 200ms ease",
":hover": {
backgroundColor: "rgba(255, 255, 255, 0.06)",
backgroundColor: t.interactiveHover,
},
})}
>
@ -384,27 +383,27 @@ export const Sidebar = memo(function Sidebar({
minWidth: 0,
flexShrink: 1,
}}
color={hasUnread ? "#ffffff" : theme.colors.contentSecondary}
color={hasUnread ? t.textPrimary : t.textSecondary}
>
{task.title}
</LabelSmall>
{task.pullRequest != null ? (
<span className={css({ display: "inline-flex", alignItems: "center", gap: "4px", flexShrink: 0 })}>
<LabelXSmall color={theme.colors.contentSecondary} $style={{ fontWeight: 600 }}>
<LabelXSmall color={t.textSecondary} $style={{ fontWeight: 600 }}>
#{task.pullRequest.number}
</LabelXSmall>
{task.pullRequest.status === "draft" ? <CloudUpload size={11} color="#ff4f00" /> : null}
{task.pullRequest.status === "draft" ? <CloudUpload size={11} color={t.accent} /> : null}
</span>
) : (
<GitPullRequestDraft size={11} color={theme.colors.contentTertiary} />
<GitPullRequestDraft size={11} color={t.textTertiary} />
)}
{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>
<span className={css({ fontSize: "11px", color: t.statusSuccess })}>+{totalAdded}</span>
<span className={css({ fontSize: "11px", color: t.statusError })}>-{totalRemoved}</span>
</div>
) : null}
<LabelXSmall color={theme.colors.contentTertiary} $style={{ flexShrink: 0, marginLeft: hasDiffs ? undefined : "auto" }}>
<LabelXSmall color={t.textTertiary} $style={{ flexShrink: 0, marginLeft: hasDiffs ? undefined : "auto" }}>
{formatRelativeAge(task.updatedAtMs)}
</LabelXSmall>
</div>
@ -422,7 +421,7 @@ export const Sidebar = memo(function Sidebar({
);
});
const menuButtonStyle = (highlight: boolean) =>
const menuButtonStyle = (highlight: boolean, tokens: FoundryTokens) =>
({
display: "flex",
alignItems: "center",
@ -431,8 +430,8 @@ const menuButtonStyle = (highlight: boolean) =>
padding: "8px 12px",
borderRadius: "6px",
border: "none",
background: highlight ? "rgba(255, 255, 255, 0.06)" : "transparent",
color: "rgba(255, 255, 255, 0.75)",
background: highlight ? tokens.interactiveHover : "transparent",
color: tokens.textSecondary,
cursor: "pointer",
fontSize: "13px",
fontWeight: 400 as const,
@ -442,6 +441,7 @@ const menuButtonStyle = (highlight: boolean) =>
function SidebarFooter() {
const [css] = useStyletron();
const t = useFoundryTokens();
const navigate = useNavigate();
const client = useMockAppClient();
const snapshot = useMockAppSnapshot();
@ -547,9 +547,9 @@ function SidebarFooter() {
const popoverStyle = css({
borderRadius: "10px",
border: "1px solid rgba(255, 255, 255, 0.10)",
backgroundColor: "#18181b",
boxShadow: "0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.04)",
border: `1px solid ${t.borderDefault}`,
backgroundColor: t.surfaceElevated,
boxShadow: `${t.shadow}, 0 0 0 1px ${t.interactiveSubtle}`,
padding: "4px",
display: "flex",
flexDirection: "column",
@ -591,11 +591,11 @@ function SidebarFooter() {
type="button"
onClick={() => setWorkspaceFlyoutOpen((prev) => !prev)}
className={css({
...menuButtonStyle(workspaceFlyoutOpen),
...menuButtonStyle(workspaceFlyoutOpen, t),
fontWeight: 500,
":hover": {
backgroundColor: "rgba(255, 255, 255, 0.06)",
color: "#ffffff",
backgroundColor: t.interactiveHover,
color: t.textPrimary,
},
})}
>
@ -610,7 +610,7 @@ function SidebarFooter() {
justifyContent: "center",
fontSize: "9px",
fontWeight: 700,
color: "#ffffff",
color: t.textOnAccent,
flexShrink: 0,
})}
>
@ -619,7 +619,7 @@ function SidebarFooter() {
<span className={css({ flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" })}>
{organization.settings.displayName}
</span>
<ChevronRight size={12} className={css({ flexShrink: 0, color: "rgba(255, 255, 255, 0.35)" })} />
<ChevronRight size={12} className={css({ flexShrink: 0, color: t.textMuted })} />
</button>
</div>
) : null}
@ -663,12 +663,12 @@ function SidebarFooter() {
}
}}
className={css({
...menuButtonStyle(isActive),
...menuButtonStyle(isActive, t),
fontWeight: isActive ? 600 : 400,
color: isActive ? "#ffffff" : "rgba(255, 255, 255, 0.65)",
color: isActive ? t.textPrimary : t.textTertiary,
":hover": {
backgroundColor: "rgba(255, 255, 255, 0.06)",
color: "#ffffff",
backgroundColor: t.interactiveHover,
color: t.textPrimary,
},
})}
>
@ -683,7 +683,7 @@ function SidebarFooter() {
justifyContent: "center",
fontSize: "9px",
fontWeight: 700,
color: "#ffffff",
color: t.textOnAccent,
flexShrink: 0,
})}
>
@ -707,11 +707,11 @@ function SidebarFooter() {
type="button"
onClick={item.onClick}
className={css({
...menuButtonStyle(false),
color: item.danger ? "#ffa198" : "rgba(255, 255, 255, 0.75)",
...menuButtonStyle(false, t),
color: item.danger ? t.statusError : t.textSecondary,
":hover": {
backgroundColor: "rgba(255, 255, 255, 0.06)",
color: item.danger ? "#ff6b6b" : "#ffffff",
backgroundColor: t.interactiveHover,
color: item.danger ? t.statusError : t.textPrimary,
},
})}
>
@ -740,13 +740,13 @@ function SidebarFooter() {
height: "28px",
borderRadius: "6px",
border: "none",
background: open ? "rgba(255, 255, 255, 0.06)" : "transparent",
color: open ? "#ffffff" : "#71717a",
background: open ? t.interactiveHover : "transparent",
color: open ? t.textPrimary : t.textTertiary,
cursor: "pointer",
transition: "all 160ms ease",
":hover": {
backgroundColor: "rgba(255, 255, 255, 0.06)",
color: "#a1a1aa",
backgroundColor: t.interactiveHover,
color: t.textSecondary,
},
})}
>

View file

@ -3,6 +3,7 @@ import { useStyletron } from "baseui";
import { LabelXSmall } from "baseui/typography";
import { FileCode, Plus, X } from "lucide-react";
import { useFoundryTokens } from "../../app/theme";
import { ContextMenuOverlay, TabAvatar, useContextMenu } from "./ui";
import { diffTabId, fileName, type Task } from "./view-model";
@ -39,7 +40,8 @@ export const TabStrip = memo(function TabStrip({
onAddTab: () => void;
sidebarCollapsed?: boolean;
}) {
const [css, theme] = useStyletron();
const [css] = useStyletron();
const t = useFoundryTokens();
const isDesktop = !!import.meta.env.VITE_DESKTOP;
const contextMenu = useContextMenu();
@ -53,9 +55,9 @@ export const TabStrip = memo(function TabStrip({
className={css({
display: "flex",
alignItems: "stretch",
borderBottom: `1px solid ${theme.colors.borderOpaque}`,
borderBottom: `1px solid ${t.borderDefault}`,
gap: "4px",
backgroundColor: "#09090b",
backgroundColor: t.surfacePrimary,
paddingLeft: sidebarCollapsed ? "14px" : "6px",
height: "41px",
minHeight: "41px",
@ -97,11 +99,11 @@ export const TabStrip = memo(function TabStrip({
marginTop: "6px",
marginBottom: "6px",
borderRadius: "8px",
backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "transparent",
backgroundColor: isActive ? t.interactiveHover : "transparent",
cursor: "pointer",
transition: "color 200ms ease, background-color 200ms ease",
flexShrink: 0,
":hover": { color: "#e4e4e7", backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.04)" },
":hover": { color: t.textPrimary, backgroundColor: isActive ? t.interactiveHover : t.interactiveSubtle },
})}
>
<div
@ -144,19 +146,19 @@ export const TabStrip = memo(function TabStrip({
maxWidth: "180px",
fontSize: "11px",
fontWeight: 600,
color: theme.colors.contentPrimary,
borderBottom: "1px solid rgba(255, 255, 255, 0.3)",
color: t.textPrimary,
borderBottom: `1px solid ${t.borderFocus}`,
})}
/>
) : (
<LabelXSmall color={isActive ? theme.colors.contentPrimary : theme.colors.contentSecondary} $style={{ fontWeight: 500 }}>
<LabelXSmall color={isActive ? t.textPrimary : t.textSecondary} $style={{ fontWeight: 500 }}>
{tab.sessionName}
</LabelXSmall>
)}
{task.tabs.length > 1 ? (
<X
size={11}
color={theme.colors.contentTertiary}
color={t.textTertiary}
data-tab-close
className={css({ cursor: "pointer", opacity: 0 })}
onClick={(event) => {
@ -190,23 +192,20 @@ export const TabStrip = memo(function TabStrip({
marginTop: "6px",
marginBottom: "6px",
borderRadius: "8px",
backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "transparent",
backgroundColor: isActive ? t.interactiveHover : "transparent",
cursor: "pointer",
transition: "color 200ms ease, background-color 200ms ease",
flexShrink: 0,
":hover": { color: "#e4e4e7", backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.04)" },
":hover": { color: t.textPrimary, backgroundColor: isActive ? t.interactiveHover : t.interactiveSubtle },
})}
>
<FileCode size={12} color={isActive ? theme.colors.contentPrimary : theme.colors.contentSecondary} />
<LabelXSmall
color={isActive ? theme.colors.contentPrimary : theme.colors.contentSecondary}
$style={{ fontWeight: 500, fontFamily: '"IBM Plex Mono", monospace' }}
>
<FileCode size={12} color={isActive ? t.textPrimary : t.textSecondary} />
<LabelXSmall color={isActive ? t.textPrimary : t.textSecondary} $style={{ fontWeight: 500, fontFamily: '"IBM Plex Mono", monospace' }}>
{fileName(path)}
</LabelXSmall>
<X
size={11}
color={theme.colors.contentTertiary}
color={t.textTertiary}
data-tab-close
className={css({ cursor: "pointer", opacity: 0 })}
onClick={(event) => {
@ -230,7 +229,7 @@ export const TabStrip = memo(function TabStrip({
flexShrink: 0,
})}
>
<Plus size={14} color={theme.colors.contentTertiary} />
<Plus size={14} color={t.textTertiary} />
</div>
</div>
{contextMenu.menu ? <ContextMenuOverlay menu={contextMenu.menu} onClose={contextMenu.close} /> : null}

View file

@ -2,6 +2,7 @@ import type { SandboxProcessRecord } from "@sandbox-agent/foundry-client";
import { ProcessTerminal } from "@sandbox-agent/react";
import { useQuery } from "@tanstack/react-query";
import { useStyletron } from "baseui";
import { useFoundryTokens } from "../../app/theme";
import { ChevronDown, Loader2, RefreshCw, Skull, SquareTerminal, Trash2, X } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { SandboxAgent } from "sandbox-agent";
@ -62,6 +63,7 @@ function formatProcessTabTitle(process: Pick<SandboxProcessRecord, "command" | "
export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
const [css] = useStyletron();
const t = useFoundryTokens();
const [activeTabId, setActiveTabId] = useState<string>(PROCESSES_TAB_ID);
const [processTabs, setProcessTabs] = useState<ProcessTab[]>([]);
const [selectedProcessId, setSelectedProcessId] = useState<string | null>(null);
@ -388,7 +390,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
alignItems: "center",
justifyContent: "center",
padding: "24px",
backgroundColor: "#080506",
backgroundColor: t.surfacePrimary,
});
const emptyCopyClassName = css({
@ -396,7 +398,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
display: "flex",
flexDirection: "column",
gap: "10px",
color: "rgba(255, 255, 255, 0.72)",
color: t.textSecondary,
fontSize: "12px",
lineHeight: 1.6,
textAlign: "center",
@ -412,13 +414,13 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
gap: "6px",
padding: "6px 10px",
borderRadius: "8px",
border: "1px solid rgba(255, 255, 255, 0.1)",
color: "#f4f4f5",
border: `1px solid ${t.borderDefault}`,
color: t.textPrimary,
cursor: "pointer",
fontSize: "11px",
fontWeight: 600,
":hover": {
backgroundColor: "rgba(255, 255, 255, 0.06)",
backgroundColor: t.interactiveHover,
},
":disabled": {
opacity: 0.45,
@ -445,7 +447,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
minHeight: 0,
display: "grid",
gridTemplateRows: "auto minmax(0, 1fr)",
backgroundColor: "#080506",
backgroundColor: t.surfacePrimary,
})}
>
<div
@ -454,7 +456,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
flexDirection: "column",
gap: "12px",
padding: "14px 14px 12px",
borderBottom: "1px solid rgba(255, 255, 255, 0.08)",
borderBottom: `1px solid ${t.borderSubtle}`,
})}
>
<div
@ -472,8 +474,8 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
gap: "2px",
})}
>
<strong className={css({ fontSize: "12px", color: "#f5f5f5" })}>Processes</strong>
<span className={css({ fontSize: "11px", color: "rgba(255, 255, 255, 0.56)" })}>
<strong className={css({ fontSize: "12px", color: t.textPrimary })}>Processes</strong>
<span className={css({ fontSize: "11px", color: t.textMuted })}>
Process lifecycle goes through the actor. Terminal transport goes straight to the sandbox.
</span>
</div>
@ -499,10 +501,10 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
<input
className={css({
width: "100%",
border: "1px solid rgba(255, 255, 255, 0.1)",
border: `1px solid ${t.borderDefault}`,
borderRadius: "8px",
backgroundColor: "#0d0a0b",
color: "#f4f4f5",
backgroundColor: t.surfaceTertiary,
color: t.textPrimary,
fontSize: "12px",
padding: "9px 10px",
})}
@ -516,10 +518,10 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
<input
className={css({
width: "100%",
border: "1px solid rgba(255, 255, 255, 0.1)",
border: `1px solid ${t.borderDefault}`,
borderRadius: "8px",
backgroundColor: "#0d0a0b",
color: "#f4f4f5",
backgroundColor: t.surfaceTertiary,
color: t.textPrimary,
fontSize: "12px",
padding: "9px 10px",
})}
@ -535,10 +537,10 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
width: "100%",
minHeight: "56px",
resize: "none",
border: "1px solid rgba(255, 255, 255, 0.1)",
border: `1px solid ${t.borderDefault}`,
borderRadius: "8px",
backgroundColor: "#0d0a0b",
color: "#f4f4f5",
backgroundColor: t.surfaceTertiary,
color: t.textPrimary,
fontSize: "12px",
padding: "9px 10px",
gridColumn: "1 / -1",
@ -552,7 +554,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
/>
</div>
<div className={css({ display: "flex", alignItems: "center", gap: "14px", fontSize: "11px", color: "rgba(255, 255, 255, 0.68)" })}>
<div className={css({ display: "flex", alignItems: "center", gap: "14px", fontSize: "11px", color: t.textSecondary })}>
<label className={css({ display: "flex", alignItems: "center", gap: "6px" })}>
<input
type="checkbox"
@ -584,7 +586,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
</button>
</div>
{createError ? <div className={css({ fontSize: "11px", color: "#fda4af" })}>{createError}</div> : null}
{createError ? <div className={css({ fontSize: "11px", color: t.statusError })}>{createError}</div> : null}
</div>
<div
@ -598,11 +600,11 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
className={css({
minHeight: 0,
overflowY: "auto",
borderRight: "1px solid rgba(255, 255, 255, 0.08)",
borderRight: `1px solid ${t.borderSubtle}`,
})}
>
{processes.length === 0 ? (
<div className={css({ padding: "16px", fontSize: "12px", color: "rgba(255,255,255,0.56)" })}>No processes yet.</div>
<div className={css({ padding: "16px", fontSize: "12px", color: t.textMuted })}>No processes yet.</div>
) : (
processes.map((process) => {
const isSelected = selectedProcessId === process.id;
@ -633,8 +635,8 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
gap: "8px",
padding: "12px 14px",
cursor: "pointer",
backgroundColor: isSelected ? "rgba(255, 255, 255, 0.06)" : "transparent",
borderBottom: "1px solid rgba(255, 255, 255, 0.06)",
backgroundColor: isSelected ? t.interactiveHover : "transparent",
borderBottom: `1px solid ${t.borderSubtle}`,
outline: "none",
":focus-visible": {
boxShadow: "inset 0 0 0 1px rgba(249, 115, 22, 0.85)",
@ -647,11 +649,11 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
width: "8px",
height: "8px",
borderRadius: "999px",
backgroundColor: process.status === "running" ? "#4ade80" : "#71717a",
backgroundColor: process.status === "running" ? t.statusSuccess : t.textTertiary,
flexShrink: 0,
})}
/>
<span className={css({ fontSize: "12px", color: "#f4f4f5", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" })}>
<span className={css({ fontSize: "12px", color: t.textPrimary, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" })}>
{formatCommandSummary(process)}
</span>
</div>
@ -662,7 +664,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
justifyContent: "space-between",
gap: "10px",
fontSize: "10px",
color: "rgba(255,255,255,0.5)",
color: t.textMuted,
})}
>
<span>{process.pid ? `PID ${process.pid}` : "PID ?"}</span>
@ -739,14 +741,14 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
flexDirection: "column",
gap: "8px",
padding: "14px",
borderBottom: "1px solid rgba(255, 255, 255, 0.08)",
borderBottom: `1px solid ${t.borderSubtle}`,
})}
>
<div className={css({ display: "flex", alignItems: "center", justifyContent: "space-between", gap: "10px" })}>
<strong className={css({ fontSize: "12px", color: "#f4f4f5" })}>{formatCommandSummary(selectedProcess)}</strong>
<span className={css({ fontSize: "10px", color: "rgba(255,255,255,0.56)" })}>{selectedProcess.status}</span>
<strong className={css({ fontSize: "12px", color: t.textPrimary })}>{formatCommandSummary(selectedProcess)}</strong>
<span className={css({ fontSize: "10px", color: t.textMuted })}>{selectedProcess.status}</span>
</div>
<div className={css({ display: "flex", flexWrap: "wrap", gap: "10px", fontSize: "10px", color: "rgba(255,255,255,0.5)" })}>
<div className={css({ display: "flex", flexWrap: "wrap", gap: "10px", fontSize: "10px", color: t.textMuted })}>
<span>{selectedProcess.pid ? `PID ${selectedProcess.pid}` : "PID ?"}</span>
<span>{selectedProcess.id}</span>
{selectedProcess.exitCode != null ? <span>exit={selectedProcess.exitCode}</span> : null}
@ -759,16 +761,16 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
alignItems: "center",
justifyContent: "space-between",
padding: "10px 14px",
borderBottom: "1px solid rgba(255,255,255,0.08)",
borderBottom: `1px solid ${t.borderSubtle}`,
})}
>
<span className={css({ fontSize: "11px", color: "rgba(255,255,255,0.68)" })}>Logs</span>
<span className={css({ fontSize: "11px", color: t.textSecondary })}>Logs</span>
<button type="button" className={smallButtonClassName} onClick={() => void refreshLogs()} disabled={logsLoading}>
{logsLoading ? <Loader2 size={11} className={css({ animation: "hf-spin 0.8s linear infinite" })} /> : <RefreshCw size={11} />}
Refresh
</button>
</div>
{logsError ? <div className={css({ padding: "14px", fontSize: "11px", color: "#fda4af" })}>{logsError}</div> : null}
{logsError ? <div className={css({ padding: "14px", fontSize: "11px", color: t.statusError })}>{logsError}</div> : null}
<pre
className={css({
flex: 1,
@ -778,7 +780,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
overflow: "auto",
fontSize: "11px",
lineHeight: 1.6,
color: "#d4d4d8",
color: t.textSecondary,
fontFamily: '"IBM Plex Mono", monospace',
})}
>
@ -827,7 +829,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
}
return (
<div className={css({ flex: 1, minHeight: 0, display: "flex", flexDirection: "column", backgroundColor: "#080506" })}>
<div className={css({ flex: 1, minHeight: 0, display: "flex", flexDirection: "column", backgroundColor: t.surfacePrimary })}>
<div
className={css({
display: "flex",
@ -835,9 +837,9 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
justifyContent: "space-between",
gap: "10px",
padding: "10px 14px",
borderBottom: "1px solid rgba(255,255,255,0.08)",
borderBottom: `1px solid ${t.borderSubtle}`,
fontSize: "11px",
color: "rgba(255,255,255,0.56)",
color: t.textMuted,
})}
>
<span>{formatCommandSummary(activeTerminalProcess)}</span>
@ -854,7 +856,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
minHeight: 0,
border: "none",
borderRadius: 0,
background: "#080506",
background: t.surfacePrimary,
}}
terminalStyle={{
minHeight: 0,
@ -910,7 +912,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
minHeight: 0,
display: "flex",
flexDirection: "column",
backgroundColor: "#080506",
backgroundColor: t.surfacePrimary,
overflow: "hidden",
})}
>
@ -921,9 +923,9 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
gap: "8px",
minHeight: "38px",
padding: "0 10px",
borderBottom: "1px solid rgba(255, 255, 255, 0.08)",
backgroundColor: "#090607",
color: "rgba(255, 255, 255, 0.72)",
borderBottom: `1px solid ${t.borderSubtle}`,
backgroundColor: t.surfaceTertiary,
color: t.textSecondary,
fontSize: "12px",
fontWeight: 600,
})}
@ -943,7 +945,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
justifyContent: "center",
width: "20px",
height: "20px",
color: "rgba(255, 255, 255, 0.56)",
color: t.textMuted,
})}
>
<ChevronDown size={14} />
@ -963,7 +965,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
alignItems: "center",
height: "100%",
padding: "0 10px",
color: activeTabId === PROCESSES_TAB_ID ? "#f5f5f5" : "rgba(255, 255, 255, 0.65)",
color: activeTabId === PROCESSES_TAB_ID ? t.textPrimary : t.textMuted,
cursor: "pointer",
":after":
activeTabId === PROCESSES_TAB_ID
@ -975,7 +977,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
bottom: 0,
height: "2px",
borderRadius: "999px",
backgroundColor: "#f5f5f5",
backgroundColor: t.textPrimary,
}
: undefined,
})}
@ -1008,7 +1010,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
gap: "6px",
height: "100%",
padding: "0 10px",
color: activeTabId === tab.id ? "#f5f5f5" : "rgba(255, 255, 255, 0.65)",
color: activeTabId === tab.id ? t.textPrimary : t.textMuted,
cursor: "pointer",
":after":
activeTabId === tab.id
@ -1020,7 +1022,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
bottom: 0,
height: "2px",
borderRadius: "999px",
backgroundColor: "#f5f5f5",
backgroundColor: t.textPrimary,
}
: undefined,
})}
@ -1044,7 +1046,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
width: "18px",
height: "18px",
marginRight: "4px",
color: "rgba(255, 255, 255, 0.42)",
color: t.textMuted,
cursor: "pointer",
})}
>
@ -1071,7 +1073,7 @@ export function TerminalPane({ workspaceId, taskId }: TerminalPaneProps) {
width: "28px",
height: "100%",
marginLeft: "2px",
color: "rgba(255, 255, 255, 0.72)",
color: t.textSecondary,
fontSize: "18px",
lineHeight: 1,
cursor: "pointer",

View file

@ -3,6 +3,7 @@ import { useStyletron } from "baseui";
import { LabelSmall } from "baseui/typography";
import { Clock, MailOpen, PanelLeft, PanelRight } from "lucide-react";
import { useFoundryTokens } from "../../app/theme";
import { PanelHeaderBar } from "./ui";
import { type AgentTab, type Task } from "./view-model";
@ -39,12 +40,13 @@ export const TranscriptHeader = memo(function TranscriptHeader({
rightSidebarCollapsed?: boolean;
onToggleRightSidebar?: () => void;
}) {
const [css, theme] = useStyletron();
const [css] = useStyletron();
const t = useFoundryTokens();
const isDesktop = !!import.meta.env.VITE_DESKTOP;
const needsTrafficLightInset = isDesktop && sidebarCollapsed;
return (
<PanelHeaderBar $style={{ backgroundColor: "#0f0f11", borderBottom: "none", paddingLeft: needsTrafficLightInset ? "74px" : "14px" }}>
<PanelHeaderBar $style={{ backgroundColor: t.surfaceSecondary, borderBottom: "none", paddingLeft: needsTrafficLightInset ? "74px" : "14px" }}>
{sidebarCollapsed && onToggleSidebar ? (
<div
className={css({
@ -55,9 +57,9 @@ export const TranscriptHeader = memo(function TranscriptHeader({
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
color: "#71717a",
color: t.textTertiary,
flexShrink: 0,
":hover": { color: "#a1a1aa", backgroundColor: "rgba(255, 255, 255, 0.06)" },
":hover": { color: t.textSecondary, backgroundColor: t.interactiveHover },
})}
onClick={onToggleSidebar}
onMouseEnter={onSidebarPeekStart}
@ -89,8 +91,8 @@ export const TranscriptHeader = memo(function TranscriptHeader({
outline: "none",
fontWeight: 500,
fontSize: "14px",
color: theme.colors.contentPrimary,
borderBottom: "1px solid rgba(255, 255, 255, 0.3)",
color: t.textPrimary,
borderBottom: `1px solid ${t.borderFocus}`,
minWidth: "80px",
maxWidth: "300px",
})}
@ -98,7 +100,7 @@ export const TranscriptHeader = memo(function TranscriptHeader({
) : (
<LabelSmall
title="Rename"
color={theme.colors.contentPrimary}
color={t.textPrimary}
$style={{ fontWeight: 400, whiteSpace: "nowrap", cursor: "pointer", ":hover": { textDecoration: "underline" } }}
onClick={() => onStartEditingField("title", task.title)}
>
@ -127,9 +129,9 @@ export const TranscriptHeader = memo(function TranscriptHeader({
outline: "none",
padding: "2px 8px",
borderRadius: "999px",
border: "1px solid rgba(255, 255, 255, 0.3)",
backgroundColor: "rgba(255, 255, 255, 0.03)",
color: "#e4e4e7",
border: `1px solid ${t.borderFocus}`,
backgroundColor: t.interactiveSubtle,
color: t.textPrimary,
fontSize: "11px",
whiteSpace: "nowrap",
fontFamily: '"IBM Plex Mono", monospace',
@ -143,14 +145,14 @@ export const TranscriptHeader = memo(function TranscriptHeader({
className={css({
padding: "2px 8px",
borderRadius: "999px",
border: "1px solid rgba(255, 255, 255, 0.14)",
backgroundColor: "rgba(255, 255, 255, 0.03)",
color: "#e4e4e7",
border: `1px solid ${t.borderMedium}`,
backgroundColor: t.interactiveSubtle,
color: t.textPrimary,
fontSize: "11px",
whiteSpace: "nowrap",
fontFamily: '"IBM Plex Mono", monospace',
cursor: "pointer",
":hover": { borderColor: "rgba(255, 255, 255, 0.3)" },
":hover": { borderColor: t.borderFocus },
})}
>
{task.branch}
@ -165,12 +167,12 @@ export const TranscriptHeader = memo(function TranscriptHeader({
gap: "5px",
padding: "3px 10px",
borderRadius: "6px",
backgroundColor: "rgba(255, 255, 255, 0.05)",
border: "1px solid rgba(255, 255, 255, 0.08)",
backgroundColor: t.interactiveHover,
border: `1px solid ${t.borderSubtle}`,
fontSize: "11px",
fontWeight: 500,
lineHeight: 1,
color: theme.colors.contentSecondary,
color: t.textSecondary,
whiteSpace: "nowrap",
})}
>
@ -195,10 +197,10 @@ export const TranscriptHeader = memo(function TranscriptHeader({
fontSize: "11px",
fontWeight: 500,
lineHeight: 1,
color: theme.colors.contentSecondary,
color: t.textSecondary,
cursor: "pointer",
transition: "all 200ms ease",
":hover": { backgroundColor: "rgba(255, 255, 255, 0.06)", color: theme.colors.contentPrimary },
":hover": { backgroundColor: t.interactiveHover, color: t.textPrimary },
})}
>
<MailOpen size={12} style={{ flexShrink: 0 }} />{" "}
@ -215,9 +217,9 @@ export const TranscriptHeader = memo(function TranscriptHeader({
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
color: "#71717a",
color: t.textTertiary,
flexShrink: 0,
":hover": { color: "#a1a1aa", backgroundColor: "rgba(255, 255, 255, 0.06)" },
":hover": { color: t.textSecondary, backgroundColor: t.interactiveHover },
})}
onClick={onToggleRightSidebar}
>

View file

@ -2,6 +2,8 @@ import { memo, useCallback, useEffect, useState, type MouseEvent } from "react";
import { styled, useStyletron } from "baseui";
import { GitPullRequest, GitPullRequestDraft } from "lucide-react";
import { useFoundryTokens } from "../../app/theme";
import { getFoundryTokens } from "../../styles/tokens";
import type { AgentKind, AgentTab } from "./view-model";
export interface ContextMenuItem {
@ -43,6 +45,7 @@ export const ContextMenuOverlay = memo(function ContextMenuOverlay({
onClose: () => void;
}) {
const [css] = useStyletron();
const t = useFoundryTokens();
return (
<div
@ -51,12 +54,12 @@ export const ContextMenuOverlay = memo(function ContextMenuOverlay({
zIndex: 9999,
top: `${menu.y}px`,
left: `${menu.x}px`,
backgroundColor: "#1a1a1d",
border: "1px solid rgba(255, 255, 255, 0.18)",
backgroundColor: t.surfaceElevated,
border: `1px solid ${t.borderMedium}`,
borderRadius: "8px",
padding: "4px 0",
minWidth: "160px",
boxShadow: "0 8px 24px rgba(0, 0, 0, 0.6)",
boxShadow: t.shadow,
})}
>
{menu.items.map((item, index) => (
@ -69,9 +72,9 @@ export const ContextMenuOverlay = memo(function ContextMenuOverlay({
className={css({
padding: "8px 14px",
fontSize: "12px",
color: "#e4e4e7",
color: t.textPrimary,
cursor: "pointer",
":hover": { backgroundColor: "rgba(255, 255, 255, 0.08)" },
":hover": { backgroundColor: t.interactiveHover },
})}
>
{item.label}
@ -82,14 +85,16 @@ export const ContextMenuOverlay = memo(function ContextMenuOverlay({
});
export const SpinnerDot = memo(function SpinnerDot({ size = 10 }: { size?: number }) {
const t = useFoundryTokens();
return (
<div
style={{
width: size,
height: size,
borderRadius: "50%",
border: "2px solid rgba(255, 79, 0, 0.25)",
borderTopColor: "#ff4f00",
border: `2px solid ${t.accentSubtle}`,
borderTopColor: t.accent,
animation: "hf-spin 0.8s linear infinite",
flexShrink: 0,
}}
@ -98,13 +103,15 @@ export const SpinnerDot = memo(function SpinnerDot({ size = 10 }: { size?: numbe
});
export const UnreadDot = memo(function UnreadDot() {
const t = useFoundryTokens();
return (
<div
style={{
width: 7,
height: 7,
borderRadius: "50%",
backgroundColor: "#ff4f00",
backgroundColor: t.accent,
flexShrink: 0,
}}
/>
@ -112,10 +119,12 @@ export const UnreadDot = memo(function UnreadDot() {
});
export const TaskIndicator = memo(function TaskIndicator({ isRunning, hasUnread, isDraft }: { isRunning: boolean; hasUnread: boolean; isDraft: boolean }) {
const t = useFoundryTokens();
if (isRunning) return <SpinnerDot size={8} />;
if (hasUnread) return <UnreadDot />;
if (isDraft) return <GitPullRequestDraft size={12} color="#a1a1aa" />;
return <GitPullRequest size={12} color="#7ee787" />;
if (isDraft) return <GitPullRequestDraft size={12} color={t.textSecondary} />;
return <GitPullRequest size={12} color={t.statusSuccess} />;
});
const ClaudeIcon = memo(function ClaudeIcon({ size = 14 }: { size?: number }) {
@ -130,21 +139,25 @@ const ClaudeIcon = memo(function ClaudeIcon({ size = 14 }: { size?: number }) {
});
const OpenAIIcon = memo(function OpenAIIcon({ size = 14 }: { size?: number }) {
const t = useFoundryTokens();
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={{ flexShrink: 0 }}>
<path
d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364l2.0153-1.1639a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"
fill="#ffffff"
fill={t.textPrimary}
/>
</svg>
);
});
const CursorIcon = memo(function CursorIcon({ size = 14 }: { size?: number }) {
const t = useFoundryTokens();
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={{ flexShrink: 0 }}>
<rect x="3" y="3" width="18" height="18" rx="4" stroke="#A1A1AA" strokeWidth="1.5" />
<path d="M8 12h8M12 8v8" stroke="#A1A1AA" strokeWidth="1.5" strokeLinecap="round" />
<rect x="3" y="3" width="18" height="18" rx="4" stroke={t.textSecondary} strokeWidth="1.5" />
<path d="M8 12h8M12 8v8" stroke={t.textSecondary} strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
});
@ -166,21 +179,27 @@ export const TabAvatar = memo(function TabAvatar({ tab }: { tab: AgentTab }) {
return <AgentIcon agent={tab.agent} size={13} />;
});
export const Shell = styled("div", ({ $theme }) => ({
display: "flex",
height: "100dvh",
backgroundColor: $theme.colors.backgroundSecondary,
overflow: "hidden",
}));
export const Shell = styled("div", ({ $theme }) => {
const t = getFoundryTokens($theme);
return {
display: "flex",
height: "100dvh",
backgroundColor: t.surfaceSecondary,
overflow: "hidden",
};
});
export const SPanel = styled("section", ({ $theme }) => ({
minHeight: 0,
flex: 1,
display: "flex",
flexDirection: "column" as const,
backgroundColor: $theme.colors.backgroundSecondary,
overflow: "hidden",
}));
export const SPanel = styled("section", ({ $theme }) => {
const t = getFoundryTokens($theme);
return {
minHeight: 0,
flex: 1,
display: "flex",
flexDirection: "column" as const,
backgroundColor: t.surfaceSecondary,
overflow: "hidden",
};
});
export const ScrollBody = styled("div", () => ({
minHeight: 0,
@ -195,16 +214,19 @@ export const HEADER_HEIGHT = "42px";
export const PROMPT_TEXTAREA_MIN_HEIGHT = 56;
export const PROMPT_TEXTAREA_MAX_HEIGHT = 100;
export const PanelHeaderBar = styled("div", ({ $theme }) => ({
display: "flex",
alignItems: "center",
minHeight: HEADER_HEIGHT,
maxHeight: HEADER_HEIGHT,
padding: "0 14px",
borderBottom: `1px solid ${$theme.colors.borderOpaque}`,
backgroundColor: $theme.colors.backgroundTertiary,
gap: "8px",
flexShrink: 0,
position: "relative" as const,
zIndex: 9999,
}));
export const PanelHeaderBar = styled("div", ({ $theme }) => {
const t = getFoundryTokens($theme);
return {
display: "flex",
alignItems: "center",
minHeight: HEADER_HEIGHT,
maxHeight: HEADER_HEIGHT,
padding: "0 14px",
borderBottom: `1px solid ${t.borderDefault}`,
backgroundColor: t.surfaceTertiary,
gap: "8px",
flexShrink: 0,
position: "relative" as const,
zIndex: 9999,
};
});