diff --git a/factory/packages/frontend/src/app/theme.ts b/factory/packages/frontend/src/app/theme.ts index 92ad3db..debc0fc 100644 --- a/factory/packages/frontend/src/app/theme.ts +++ b/factory/packages/frontend/src/app/theme.ts @@ -4,15 +4,15 @@ export const appTheme: Theme = createDarkTheme({ colors: { primary: "#e4e4e7", // zinc-200 accent: "#ff4f00", // orange accent (inspector) - backgroundPrimary: "#000000", // pure black (inspector --bg) - backgroundSecondary: "#0a0a0b", // near-black panels (inspector --bg-panel) - backgroundTertiary: "#0a0a0b", // same as panel (border provides separation) + backgroundPrimary: "#09090b", // darkest — chat center panel + backgroundSecondary: "#0f0f11", // slightly lighter — sidebars + backgroundTertiary: "#0c0c0e", // center + right panel headers backgroundInversePrimary: "#fafafa", contentPrimary: "#ffffff", // white (inspector --text) contentSecondary: "#a1a1aa", // zinc-400 (inspector --muted) contentTertiary: "#71717a", // zinc-500 contentInversePrimary: "#000000", - borderOpaque: "rgba(255, 255, 255, 0.18)", // inspector --border - borderTransparent: "rgba(255, 255, 255, 0.14)", // inspector --border-2 + borderOpaque: "rgba(255, 255, 255, 0.10)", // inspector --border + borderTransparent: "rgba(255, 255, 255, 0.07)", // inspector --border-2 }, }); diff --git a/factory/packages/frontend/src/components/mock-layout.tsx b/factory/packages/frontend/src/components/mock-layout.tsx index 5fdff9d..dd4936e 100644 --- a/factory/packages/frontend/src/components/mock-layout.tsx +++ b/factory/packages/frontend/src/components/mock-layout.tsx @@ -443,6 +443,7 @@ const TranscriptPanel = memo(function TranscriptPanel({ } }} /> +
) : null} +
); }); @@ -560,7 +562,23 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId } handoffWorkbenchClient.getSnapshot.bind(handoffWorkbenchClient), ); const handoffs = viewModel.handoffs ?? []; - const projects = viewModel.projects ?? []; + const rawProjects = viewModel.projects ?? []; + const [projectOrder, setProjectOrder] = useState(null); + const projects = useMemo(() => { + if (!projectOrder) return rawProjects; + const byId = new Map(rawProjects.map((p) => [p.id, p])); + const ordered = projectOrder.map((id) => byId.get(id)).filter(Boolean) as typeof rawProjects; + for (const p of rawProjects) { + if (!projectOrder.includes(p.id)) ordered.push(p); + } + return ordered; + }, [rawProjects, projectOrder]); + const reorderProjects = useCallback((fromIndex: number, toIndex: number) => { + const ids = projects.map((p) => p.id); + const [moved] = ids.splice(fromIndex, 1); + ids.splice(toIndex, 0, moved!); + setProjectOrder(ids); + }, [projects]); const [activeTabIdByHandoff, setActiveTabIdByHandoff] = useState>({}); const [lastAgentTabIdByHandoff, setLastAgentTabIdByHandoff] = useState>({}); const [openDiffsByHandoff, setOpenDiffsByHandoff] = useState>({}); @@ -864,34 +882,35 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId } >
-
-
Onboarding
-

Give us support for sandbox agent

-

- Before you keep going, give us support for sandbox agent and star the repo right here in the app. +

+
Welcome to Foundry
+

Support Sandbox Agent

+

+ Star the repo to help us grow and stay up to date with new releases.

{starRepoError ? (
@@ -899,18 +918,20 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId }
) : null} -
+
@@ -949,8 +972,9 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId } onMarkUnread={markHandoffUnread} onRenameHandoff={renameHandoff} onRenameBranch={renameBranch} + onReorderProjects={reorderProjects} /> - +
0 ? "#ff4f00" : "#444", - color: "#fff", + background: viewModel.repos.length > 0 ? "rgba(255, 255, 255, 0.12)" : "#444", + color: "#e4e4e7", cursor: viewModel.repos.length > 0 ? "pointer" : "not-allowed", fontWeight: 600, }} @@ -1015,6 +1039,7 @@ export function MockLayout({ workspaceId, selectedHandoffId, selectedSessionId } onMarkUnread={markHandoffUnread} onRenameHandoff={renameHandoff} onRenameBranch={renameBranch} + onReorderProjects={reorderProjects} /> - {isCopied ? "Copied" : "Copy"} + {isCopied ? "Copied" : null}
diff --git a/factory/packages/frontend/src/components/mock-layout/prompt-composer.tsx b/factory/packages/frontend/src/components/mock-layout/prompt-composer.tsx index d036d03..970a94a 100644 --- a/factory/packages/frontend/src/components/mock-layout/prompt-composer.tsx +++ b/factory/packages/frontend/src/components/mock-layout/prompt-composer.tsx @@ -1,7 +1,7 @@ import { memo, type Ref } from "react"; import { useStyletron } from "baseui"; import { ChatComposer, type ChatComposerClassNames } from "@sandbox-agent/react"; -import { ArrowUpFromLine, FileCode, Square, X } from "lucide-react"; +import { FileCode, SendHorizonal, Square, X } from "lucide-react"; import { ModelPicker } from "./model-picker"; import { PROMPT_TEXTAREA_MAX_HEIGHT, PROMPT_TEXTAREA_MIN_HEIGHT } from "./ui"; @@ -45,7 +45,7 @@ export const PromptComposer = memo(function PromptComposer({ borderRadius: "16px", minHeight: `${PROMPT_TEXTAREA_MIN_HEIGHT + 36}px`, transition: "border-color 200ms ease", - ":focus-within": { borderColor: "rgba(255, 255, 255, 0.3)" }, + ":focus-within": { borderColor: "rgba(255, 255, 255, 0.15)" }, display: "flex", flexDirection: "column", }), @@ -82,9 +82,9 @@ export const PromptComposer = memo(function PromptComposer({ justifyContent: "center", color: theme.colors.contentPrimary, transition: "background 200ms ease", - backgroundColor: isRunning ? "rgba(255, 255, 255, 0.06)" : "#ff4f00", + backgroundColor: isRunning ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.12)", ":hover": { - backgroundColor: isRunning ? "rgba(255, 255, 255, 0.12)" : "#ff6a00", + backgroundColor: isRunning ? "rgba(255, 255, 255, 0.12)" : "rgba(255, 255, 255, 0.20)", }, ":disabled": { cursor: "not-allowed", @@ -157,7 +157,7 @@ export const PromptComposer = memo(function PromptComposer({ allowEmptySubmit={isRunning} submitLabel={isRunning ? "Stop" : "Send"} classNames={composerClassNames} - renderSubmitContent={() => (isRunning ? : )} + renderSubmitContent={() => (isRunning ? : )} renderFooter={() => (
- + +
{!isTerminal ? (
@@ -208,12 +208,14 @@ export const RightSidebar = memo(function RightSidebar({ ) : null} +
Changes @@ -268,19 +272,20 @@ export const RightSidebar = memo(function RightSidebar({ all: "unset", display: "flex", alignItems: "center", - height: "100%", - padding: "0 16px", + padding: "4px 12px", + marginTop: "6px", + marginBottom: "6px", + borderRadius: "8px", cursor: "pointer", fontSize: "12px", - fontWeight: 600, + fontWeight: 500, whiteSpace: "nowrap", color: rightTab === "files" ? theme.colors.contentPrimary : theme.colors.contentSecondary, - borderBottom: `2px solid ${rightTab === "files" ? "#ff4f00" : "transparent"}`, - marginBottom: "-1px", - transitionProperty: "color, border-color", + backgroundColor: rightTab === "files" ? "rgba(255, 255, 255, 0.06)" : "transparent", + transitionProperty: "color, background-color", transitionDuration: "200ms", transitionTimingFunction: "ease", - ":hover": { color: "#e4e4e7" }, + ":hover": { color: "#e4e4e7", backgroundColor: rightTab === "files" ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.04)" }, })} > All Files @@ -360,6 +365,7 @@ export const RightSidebar = memo(function RightSidebar({
)} +
{contextMenu.menu ? : null} ); diff --git a/factory/packages/frontend/src/components/mock-layout/sidebar.tsx b/factory/packages/frontend/src/components/mock-layout/sidebar.tsx index 2141fea..4aa76a8 100644 --- a/factory/packages/frontend/src/components/mock-layout/sidebar.tsx +++ b/factory/packages/frontend/src/components/mock-layout/sidebar.tsx @@ -1,4 +1,4 @@ -import { memo, useState } from "react"; +import { memo, useRef, useState } from "react"; import { useStyletron } from "baseui"; import { LabelSmall, LabelXSmall } from "baseui/typography"; import { ChevronDown, ChevronUp, CloudUpload, GitPullRequestDraft, ListChecks, Plus } from "lucide-react"; @@ -30,6 +30,7 @@ export const Sidebar = memo(function Sidebar({ onMarkUnread, onRenameHandoff, onRenameBranch, + onReorderProjects, }: { projects: ProjectSection[]; activeId: string; @@ -38,10 +39,13 @@ export const Sidebar = memo(function Sidebar({ onMarkUnread: (id: string) => void; onRenameHandoff: (id: string) => void; onRenameBranch: (id: string) => void; + onReorderProjects: (fromIndex: number, toIndex: number) => void; }) { const [css, theme] = useStyletron(); const contextMenu = useContextMenu(); const [collapsedProjects, setCollapsedProjects] = useState>({}); + const dragIndexRef = useRef(null); + const [dragOverIndex, setDragOverIndex] = useState(null); return ( @@ -53,10 +57,10 @@ export const Sidebar = memo(function Sidebar({ display: none !important; } `} - + Tasks @@ -65,17 +69,17 @@ export const Sidebar = memo(function Sidebar({ onClick={onCreate} className={css({ all: "unset", - width: "24px", - height: "24px", - borderRadius: "4px", - backgroundColor: "#ff4f00", - color: "#ffffff", + width: "26px", + height: "26px", + borderRadius: "8px", + backgroundColor: "rgba(255, 255, 255, 0.12)", + color: "#e4e4e7", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", transition: "background 200ms ease", - ":hover": { backgroundColor: "#ff6a00" }, + ":hover": { backgroundColor: "rgba(255, 255, 255, 0.20)" }, })} > @@ -83,11 +87,48 @@ export const Sidebar = memo(function Sidebar({
- {projects.map((project) => { + {projects.map((project, projectIndex) => { const isCollapsed = collapsedProjects[project.id] === true; + const isDragOver = dragOverIndex === projectIndex && dragIndexRef.current !== projectIndex; return ( -
+
{ + dragIndexRef.current = projectIndex; + event.dataTransfer.effectAllowed = "move"; + event.dataTransfer.setData("text/plain", String(projectIndex)); + }} + onDragOver={(event) => { + event.preventDefault(); + event.dataTransfer.dropEffect = "move"; + setDragOverIndex(projectIndex); + }} + onDragLeave={() => { + setDragOverIndex((current) => (current === projectIndex ? null : current)); + }} + onDrop={(event) => { + event.preventDefault(); + const fromIndex = dragIndexRef.current; + if (fromIndex != null && fromIndex !== projectIndex) { + onReorderProjects(fromIndex, projectIndex); + } + dragIndexRef.current = null; + setDragOverIndex(null); + }} + onDragEnd={() => { + dragIndexRef.current = null; + setDragOverIndex(null); + }} + className={css({ + display: "flex", + flexDirection: "column", + gap: "4px", + borderTop: isDragOver ? "2px solid #ff4f00" : "2px solid transparent", + transition: "border-color 150ms ease", + })} + >
setCollapsedProjects((current) => ({ @@ -102,7 +143,7 @@ export const Sidebar = memo(function Sidebar({ justifyContent: "space-between", padding: "10px 8px 4px", gap: "8px", - cursor: "pointer", + cursor: "grab", userSelect: "none", ":hover": { opacity: 0.8 }, })} @@ -150,9 +191,11 @@ export const Sidebar = memo(function Sidebar({ {project.label}
- - {formatRelativeAge(project.updatedAtMs)} - + {isCollapsed ? ( + + {formatRelativeAge(project.updatedAtMs)} + + ) : null}
{!isCollapsed && project.handoffs.map((handoff) => { @@ -177,7 +220,7 @@ export const Sidebar = memo(function Sidebar({ ]) } className={css({ - padding: "12px", + padding: "8px 12px", borderRadius: "8px", border: "1px solid transparent", backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "transparent", @@ -204,35 +247,17 @@ export const Sidebar = memo(function Sidebar({
{handoff.title} - {hasDiffs ? ( -
- +{totalAdded} - -{totalRemoved} -
- ) : null} -
-
- - {handoff.repoName} - {handoff.pullRequest != null ? ( @@ -243,7 +268,13 @@ export const Sidebar = memo(function Sidebar({ ) : ( )} - + {hasDiffs ? ( +
+ +{totalAdded} + -{totalRemoved} +
+ ) : null} + {formatRelativeAge(handoff.updatedAtMs)}
diff --git a/factory/packages/frontend/src/components/mock-layout/tab-strip.tsx b/factory/packages/frontend/src/components/mock-layout/tab-strip.tsx index c540349..9f0cd50 100644 --- a/factory/packages/frontend/src/components/mock-layout/tab-strip.tsx +++ b/factory/packages/frontend/src/components/mock-layout/tab-strip.tsx @@ -42,12 +42,18 @@ export const TabStrip = memo(function TabStrip({ return ( <> +
1 ? [{ label: "Close tab", onClick: () => onCloseTab(tab.id) }] : []), ]) } + data-tab className={css({ display: "flex", alignItems: "center", gap: "6px", - padding: "0 14px", - borderBottom: isActive ? "2px solid #ff4f00" : "2px solid transparent", + padding: "4px 12px", + marginTop: "6px", + marginBottom: "6px", + borderRadius: "8px", + backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "transparent", cursor: "pointer", - transition: "color 200ms ease, border-color 200ms ease", + transition: "color 200ms ease, background-color 200ms ease", flexShrink: 0, - ":hover": { color: "#e4e4e7" }, + ":hover": { color: "#e4e4e7", backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.04)" }, })} >
) : ( - + {tab.sessionName} )} @@ -138,7 +148,8 @@ export const TabStrip = memo(function TabStrip({ { event.stopPropagation(); onCloseTab(tab.id); @@ -161,29 +172,34 @@ export const TabStrip = memo(function TabStrip({ onCloseDiffTab(path); } }} + data-tab className={css({ display: "flex", alignItems: "center", gap: "6px", - padding: "0 14px", - borderBottom: "2px solid transparent", + padding: "4px 12px", + marginTop: "6px", + marginBottom: "6px", + borderRadius: "8px", + backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "transparent", cursor: "pointer", - transition: "color 200ms ease, border-color 200ms ease", + transition: "color 200ms ease, background-color 200ms ease", flexShrink: 0, - ":hover": { color: "#e4e4e7" }, + ":hover": { color: "#e4e4e7", backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.04)" }, })} > {fileName(path)} { event.stopPropagation(); onCloseDiffTab(path); diff --git a/factory/packages/frontend/src/components/mock-layout/transcript-header.tsx b/factory/packages/frontend/src/components/mock-layout/transcript-header.tsx index 820cff6..56a9c76 100644 --- a/factory/packages/frontend/src/components/mock-layout/transcript-header.tsx +++ b/factory/packages/frontend/src/components/mock-layout/transcript-header.tsx @@ -30,7 +30,7 @@ export const TranscriptHeader = memo(function TranscriptHeader({ const [css, theme] = useStyletron(); return ( - + {editingField === "title" ? ( onStartEditingField("title", handoff.title)} > {handoff.title} diff --git a/factory/packages/frontend/src/components/mock-layout/ui.tsx b/factory/packages/frontend/src/components/mock-layout/ui.tsx index e517410..90d205c 100644 --- a/factory/packages/frontend/src/components/mock-layout/ui.tsx +++ b/factory/packages/frontend/src/components/mock-layout/ui.tsx @@ -178,8 +178,8 @@ export const Shell = styled("div", ({ $theme }) => ({ display: "grid", gap: "1px", height: "100dvh", - backgroundColor: $theme.colors.borderOpaque, - gridTemplateColumns: "280px minmax(0, 1fr) 380px", + backgroundColor: $theme.colors.backgroundSecondary, + gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1.5fr) 380px", overflow: "hidden", }));