mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-18 01:00:32 +00:00
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:
parent
ed6e6f6fa5
commit
f09b9090bb
17 changed files with 887 additions and 523 deletions
|
|
@ -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",
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
})}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue