-
+ {showSearch ? (
+
+ ) : null}
{hasSearch ? (
diff --git a/packages/web/src/features/workspace/views/shared/git-panel.test.tsx b/packages/web/src/features/workspace/views/shared/git-panel.test.tsx
index 9e3381a9..e180210c 100644
--- a/packages/web/src/features/workspace/views/shared/git-panel.test.tsx
+++ b/packages/web/src/features/workspace/views/shared/git-panel.test.tsx
@@ -2215,6 +2215,112 @@ describe("GitPanel", () => {
expect(otherTextarea?.value).toBe("");
});
+ it("restores git panel instance state per workspace tab instance", async () => {
+ const sendCommand = vi.fn().mockImplementation(async (op: string) => {
+ if (op === "git.status") {
+ return status;
+ }
+
+ if (op === "git.branches") {
+ return { current: "feature/ai-agent", branches: [] };
+ }
+
+ if (op === "worktree.list") {
+ return {
+ worktrees,
+ };
+ }
+
+ if (op === "git.log") {
+ return {
+ entries: historyEntries,
+ };
+ }
+
+ return {};
+ });
+
+ const store = createStore();
+ store.set(localeAtom, "en");
+ store.set(wsClientAtom, { sendCommand } as never);
+ store.set(workspacesAtom, {
+ "ws-a": {
+ id: "ws-a",
+ path: "/repo/a",
+ targetRuntime: "native",
+ openedAt: 1,
+ lastActiveAt: 1,
+ uiState: {
+ leftPanelWidth: 280,
+ bottomPanelHeight: 200,
+ focusMode: false,
+ },
+ },
+ "ws-b": {
+ id: "ws-b",
+ path: "/repo/b",
+ targetRuntime: "native",
+ openedAt: 2,
+ lastActiveAt: 2,
+ uiState: {
+ leftPanelWidth: 280,
+ bottomPanelHeight: 200,
+ focusMode: false,
+ },
+ },
+ } as never);
+
+ const { rerender } = render(
+
+
+
+ );
+
+ fireEvent.click(await screen.findByRole("button", { name: /Worktrees/ }));
+ fireEvent.click(screen.getByRole("button", { name: "History" }));
+ fireEvent.click(screen.getByRole("button", { name: "New" }));
+
+ expect(await screen.findByText("pr/123-fix-auth")).toBeInTheDocument();
+ expect(await screen.findByText("feat: refresh source control surface")).toBeInTheDocument();
+ expect(await screen.findByLabelText("Branch")).toBeInTheDocument();
+
+ rerender(
+
+
+
+ );
+
+ expect(await screen.findByRole("button", { name: /Worktrees/ })).toHaveAttribute(
+ "aria-expanded",
+ "false"
+ );
+ expect(screen.getByRole("button", { name: "History" })).toHaveAttribute(
+ "aria-expanded",
+ "false"
+ );
+ expect(screen.queryByText("pr/123-fix-auth")).toBeNull();
+ expect(screen.queryByText("feat: refresh source control surface")).toBeNull();
+ expect(screen.queryByLabelText("Branch")).toBeNull();
+
+ rerender(
+
+
+
+ );
+
+ expect(await screen.findByRole("button", { name: /Worktrees/ })).toHaveAttribute(
+ "aria-expanded",
+ "true"
+ );
+ expect(screen.getByRole("button", { name: "History" })).toHaveAttribute(
+ "aria-expanded",
+ "true"
+ );
+ expect(await screen.findByText("pr/123-fix-auth")).toBeInTheDocument();
+ expect(await screen.findByText("feat: refresh source control surface")).toBeInTheDocument();
+ expect(await screen.findByLabelText("Branch")).toBeInTheDocument();
+ });
+
it("clears the persisted commit draft for the workspace after a successful commit", async () => {
const sendCommand = vi.fn().mockImplementation(async (op: string) => {
if (op === "git.status") {
diff --git a/packages/web/src/features/workspace/views/shared/git-panel.tsx b/packages/web/src/features/workspace/views/shared/git-panel.tsx
index 9a7d0209..d90095d9 100644
--- a/packages/web/src/features/workspace/views/shared/git-panel.tsx
+++ b/packages/web/src/features/workspace/views/shared/git-panel.tsx
@@ -1,8 +1,9 @@
import type { GitCommitSummary, GitFileChange, WorktreeInfo } from "@coder-studio/core";
-import { useAtomValue } from "jotai";
+import { atom, useAtom, useAtomValue } from "jotai";
+import { atomFamily } from "jotai-family";
import { ChevronDown, Minus, Plus, RotateCcw } from "lucide-react";
import type { FC, MouseEvent, ReactNode } from "react";
-import { useEffect, useMemo, useRef, useState } from "react";
+import { useEffect, useMemo, useRef } from "react";
import { localeAtom } from "../../../../atoms/app-ui";
import {
ConfirmDialog,
@@ -58,6 +59,42 @@ interface GitPanelProps {
variant?: "desktop" | "mobile";
}
+interface GitPanelState {
+ worktreeSurfaceView: "list" | "create" | null;
+ worktreesExpanded: boolean;
+ historyExpanded: boolean;
+ collapsedGroups: Record
;
+}
+
+function createInitialCollapsedGroups(isMobile: boolean): Record {
+ return isMobile
+ ? {
+ staged: false,
+ changes: true,
+ }
+ : {
+ staged: false,
+ changes: false,
+ };
+}
+
+function createInitialGitPanelState(isMobile: boolean): GitPanelState {
+ return {
+ worktreeSurfaceView: null,
+ worktreesExpanded: false,
+ historyExpanded: false,
+ collapsedGroups: createInitialCollapsedGroups(isMobile),
+ };
+}
+
+function getGitPanelStateKey(workspaceId: string, variant: "desktop" | "mobile"): string {
+ return `${workspaceId}::${variant}`;
+}
+
+const gitPanelStateAtomFamily = atomFamily((stateKey: string) =>
+ atom(createInitialGitPanelState(stateKey.endsWith("::mobile")))
+);
+
export const GitPanel: FC = ({
workspaceId,
refreshToken = 0,
@@ -65,6 +102,9 @@ export const GitPanel: FC = ({
variant = "desktop",
}) => {
const isMobile = variant === "mobile";
+ const [panelState, setPanelState] = useAtom(
+ gitPanelStateAtomFamily(getGitPanelStateKey(workspaceId, variant))
+ );
const locale = useAtomValue(localeAtom) === "zh" ? "zh" : "en";
const t = useTranslation();
const {
@@ -95,21 +135,8 @@ export const GitPanel: FC = ({
});
const { currentWorktree, hasWorkspace, list, loadWorktrees, openWorktree } =
useWorktreeManagementActions(workspaceId);
- const [worktreeSurfaceView, setWorktreeSurfaceView] = useState<"list" | "create" | null>(null);
- const [worktreesExpanded, setWorktreesExpanded] = useState(false);
- const [historyExpanded, setHistoryExpanded] = useState(false);
const worktreeAutoLoadAttemptedRef = useRef(false);
- const [collapsedGroups, setCollapsedGroups] = useState>(() =>
- isMobile
- ? {
- staged: false,
- changes: true,
- }
- : {
- staged: false,
- changes: false,
- }
- );
+ const { collapsedGroups, historyExpanded, worktreeSurfaceView, worktreesExpanded } = panelState;
useEffect(() => {
worktreeAutoLoadAttemptedRef.current = false;
@@ -134,7 +161,10 @@ export const GitPanel: FC = ({
const handleWorktreeOpen = async (worktree: WorktreeInfo) => {
if (currentWorktree?.path === worktree.path) {
- setWorktreeSurfaceView("list");
+ setPanelState((current) => ({
+ ...current,
+ worktreeSurfaceView: "list",
+ }));
return;
}
@@ -180,7 +210,12 @@ export const GitPanel: FC = ({
diff --git a/packages/web/src/shells/mobile-shell/index.test.tsx b/packages/web/src/shells/mobile-shell/index.test.tsx
index 71a2bd4c..bd7ac654 100644
--- a/packages/web/src/shells/mobile-shell/index.test.tsx
+++ b/packages/web/src/shells/mobile-shell/index.test.tsx
@@ -776,12 +776,12 @@ describe("MobileShell Phase 2 workspace", () => {
await user.click(screen.getByRole("button", { name: "Open Files sheet" }));
expect(screen.getByRole("tablist", { name: "Files sheet tabs" })).toBeInTheDocument();
- expect(screen.getByRole("tab", { name: "Files" })).toHaveAttribute("aria-selected", "true");
+ expect(screen.getByRole("tab", { name: "Explorer" })).toHaveAttribute("aria-selected", "true");
expect(screen.queryByRole("button", { name: "Close current sheet" })).not.toBeInTheDocument();
await user.click(screen.getByRole("button", { name: /back|返回/i }));
- expect(screen.queryByRole("tab", { name: "Files" })).not.toBeInTheDocument();
+ expect(screen.queryByRole("tab", { name: "Explorer" })).not.toBeInTheDocument();
});
it("shows the current branch name in the shared workspace footer", async () => {
@@ -3085,7 +3085,7 @@ describe("MobileShell Phase 2 workspace", () => {
await user.click(screen.getByRole("button", { name: "打开文件面板" }));
- expect(screen.getByRole("region", { name: "文件面板" })).toBeInTheDocument();
+ expect(screen.getByRole("region", { name: "资源管理器面板" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "关闭当前面板" })).toBeInTheDocument();
});
@@ -3161,10 +3161,10 @@ describe("MobileShell Phase 2 workspace", () => {
renderMobileShell();
await user.click(screen.getByRole("button", { name: "Open Files sheet" }));
- expect(screen.getByRole("region", { name: "Files sheet" })).toHaveClass(
+ expect(screen.getByRole("region", { name: "Explorer sheet" })).toHaveClass(
"mobile-sheet--fullscreen"
);
- expect(screen.getByRole("tab", { name: "Files" })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /^new file$|^新建文件$/i })).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "mock-file-tree" }));
expect(screen.getByTestId("mobile-code-editor")).toBeInTheDocument();
@@ -3183,8 +3183,11 @@ describe("MobileShell Phase 2 workspace", () => {
renderMobileShell();
await user.click(screen.getByRole("button", { name: "Open Files sheet" }));
- await user.click(screen.getByRole("tab", { name: "Git" }));
- expect(screen.getByRole("tab", { name: "Git" })).toHaveAttribute("aria-selected", "true");
+ await user.click(screen.getByRole("tab", { name: /Source Control|源代码管理/i }));
+ expect(screen.getByRole("tab", { name: /Source Control|源代码管理/i })).toHaveAttribute(
+ "aria-selected",
+ "true"
+ );
await user.click(screen.getByRole("button", { name: "mock-git-panel" }));
expect(screen.getByTestId("mobile-code-editor")).toBeInTheDocument();
@@ -3195,11 +3198,11 @@ describe("MobileShell Phase 2 workspace", () => {
renderMobileShell();
await user.click(screen.getByRole("button", { name: "Open Files sheet" }));
- await user.click(screen.getByRole("tab", { name: "Git" }));
+ await user.click(screen.getByRole("tab", { name: /Source Control|源代码管理/i }));
await user.click(screen.getByRole("button", { name: "mock-git-history" }));
expect(screen.getByTestId("mobile-code-editor")).toBeInTheDocument();
- expect(screen.getByText("abc123 · commit subject")).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /back|返回/i })).toBeInTheDocument();
});
it("shows mobile diff preview and edit mode actions in the unified detail header", async () => {
@@ -3219,15 +3222,20 @@ describe("MobileShell Phase 2 workspace", () => {
expect(mockMobileEditorSetMode).toHaveBeenCalledWith("edit");
});
- it("shows file actions in the tab row only on the files tab", async () => {
+ it("shows file actions inside the workspace section only on the explorer tab", async () => {
const user = userEvent.setup();
renderMobileShell();
await user.click(screen.getByRole("button", { name: "Open Files sheet" }));
- expect(screen.getByRole("button", { name: /^new file$|^新建文件$/i })).toBeInTheDocument();
+ const workspaceSection = screen
+ .getByRole("heading", { level: 2, name: /workspace|工作区/i })
+ .closest("section") as HTMLElement;
+ expect(
+ within(workspaceSection).getByRole("button", { name: /^new file$|^新建文件$/i })
+ ).toBeInTheDocument();
expect(screen.queryByRole("button", { name: /refresh|刷新/i })).toBeNull();
- await user.click(screen.getByRole("tab", { name: "Git" }));
+ await user.click(screen.getByRole("tab", { name: /Source Control|源代码管理/i }));
expect(screen.queryByRole("button", { name: /^new file$|^新建文件$/i })).toBeNull();
});
@@ -3237,8 +3245,11 @@ describe("MobileShell Phase 2 workspace", () => {
renderMobileShell();
await user.click(screen.getByRole("button", { name: "Open Files sheet" }));
- await user.click(screen.getByRole("tab", { name: "Git" }));
- expect(screen.getByRole("tab", { name: "Git" })).toHaveAttribute("aria-selected", "true");
+ await user.click(screen.getByRole("tab", { name: /Source Control|源代码管理/i }));
+ expect(screen.getByRole("tab", { name: /Source Control|源代码管理/i })).toHaveAttribute(
+ "aria-selected",
+ "true"
+ );
await user.click(screen.getByRole("button", { name: "mock-git-panel" }));
expect(screen.getByTestId("mobile-code-editor")).toBeInTheDocument();
@@ -3256,8 +3267,11 @@ describe("MobileShell Phase 2 workspace", () => {
const { store } = renderMobileShell();
await user.click(screen.getByRole("button", { name: "Open Files sheet" }));
- await user.click(screen.getByRole("tab", { name: "Git" }));
- expect(screen.getByRole("tab", { name: "Git" })).toHaveAttribute("aria-selected", "true");
+ await user.click(screen.getByRole("tab", { name: /Source Control|源代码管理/i }));
+ expect(screen.getByRole("tab", { name: /Source Control|源代码管理/i })).toHaveAttribute(
+ "aria-selected",
+ "true"
+ );
store.set(gitDiffPreviewAtomFamily("ws-1"), {
path: "src/app.tsx",
diff --git a/packages/web/src/styles/components.css b/packages/web/src/styles/components.css
index 9e9403f6..f364becf 100644
--- a/packages/web/src/styles/components.css
+++ b/packages/web/src/styles/components.css
@@ -187,6 +187,129 @@
min-height: min(82dvh, 700px);
}
+.quick-open {
+ display: flex;
+ width: min(640px, calc(100vw - var(--sp-6)));
+ max-height: min(72vh, 520px);
+ flex-direction: column;
+ overflow: hidden;
+ border: 1px solid var(--surface-overlay-border);
+ border-radius: var(--radius-overlay);
+ background: var(--surface-overlay-bg);
+ box-shadow: var(--surface-overlay-shadow);
+}
+
+.quick-open__search {
+ display: flex;
+ align-items: center;
+ gap: var(--gap-default);
+ padding: calc(var(--sp-3) - var(--gap-compact)) var(--sp-4);
+ border-bottom: 1px solid color-mix(in srgb, var(--border) 82%, transparent);
+ background: color-mix(in srgb, var(--bg-surface) 66%, transparent);
+}
+
+.quick-open__icon {
+ flex-shrink: 0;
+ color: var(--text-tertiary);
+}
+
+.quick-open__input {
+ flex: 1;
+ min-width: 0;
+ min-height: 30px;
+ border: none;
+ background: transparent;
+ color: var(--text-primary);
+ font-size: var(--type-body-3-size);
+ line-height: var(--type-body-3-line-height);
+ font-weight: var(--type-body-3-weight);
+ outline: none;
+}
+
+.quick-open__input::placeholder {
+ color: color-mix(in srgb, var(--text-tertiary) 92%, var(--bg-panel));
+}
+
+.quick-open__list {
+ display: flex;
+ min-height: 0;
+ flex-direction: column;
+ overflow-y: auto;
+ padding: var(--sp-1) 0 var(--sp-2);
+}
+
+.quick-open__state,
+.quick-open__empty {
+ padding: var(--sp-5) var(--sp-4);
+ color: var(--text-tertiary);
+ font-size: var(--type-body-5-size);
+ line-height: var(--type-body-5-line-height);
+ font-weight: var(--type-body-5-weight);
+}
+
+.quick-open__item {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--gap-compact);
+ width: 100%;
+ padding: calc(var(--sp-2) + var(--gap-hairline)) var(--sp-4)
+ calc(var(--sp-3) - var(--gap-compact));
+ border: none;
+ background: transparent;
+ color: var(--text-primary);
+ text-align: left;
+ box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--border) 96%, transparent);
+ transition:
+ background-color var(--duration-fast) var(--ease-out),
+ color var(--duration-fast) var(--ease-out);
+}
+
+.quick-open__item:hover,
+.quick-open__item--active,
+.quick-open__item[aria-selected="true"] {
+ background: color-mix(in srgb, var(--accent-blue) 12%, var(--bg-panel));
+}
+
+.quick-open__item:focus-visible {
+ outline: none;
+ background: color-mix(in srgb, var(--accent-blue) 14%, var(--bg-panel));
+ box-shadow:
+ inset 0 0 0 var(--state-focus-ring-width)
+ color-mix(in srgb, var(--border-focus) 64%, transparent),
+ inset 0 -1px 0 color-mix(in srgb, var(--border) 96%, transparent);
+}
+
+.quick-open__item:last-child {
+ box-shadow: none;
+}
+
+.quick-open__primary {
+ min-width: 0;
+ overflow: hidden;
+ color: var(--text-primary);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: var(--type-body-3-size);
+ line-height: var(--type-body-3-line-height);
+ font-weight: var(--type-body-3-weight);
+}
+
+.quick-open__secondary {
+ min-width: 0;
+ overflow: hidden;
+ color: var(--text-secondary);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: var(--type-body-5-size);
+ line-height: var(--type-body-5-line-height);
+ font-weight: var(--type-body-5-weight);
+}
+
+.quick-open__item--active .quick-open__secondary,
+.quick-open__item[aria-selected="true"] .quick-open__secondary {
+ color: color-mix(in srgb, var(--accent-blue) 52%, var(--text-secondary));
+}
+
@media (prefers-reduced-motion: reduce) {
.command-palette,
.mobile-sheet-layer__backdrop,
@@ -12320,50 +12443,71 @@ textarea.input {
display: flex;
min-height: 0;
height: 100%;
- flex-direction: column;
+ flex-direction: row;
background: var(--bg-panel);
}
-.workspace-sidebar-panel__tabs {
+.workspace-activity-bar {
display: flex;
+ width: 52px;
+ flex-direction: column;
align-items: center;
- gap: var(--gap-default);
- min-width: 0;
+ gap: var(--sp-2);
+ padding: var(--sp-3) var(--sp-2);
+ border-right: 1px solid var(--border);
+ background: color-mix(in srgb, var(--bg-panel) 88%, var(--bg-page));
}
-.workspace-sidebar-panel__tab {
- position: relative;
+.workspace-activity-bar__button {
display: inline-flex;
+ width: 100%;
+ min-height: 40px;
align-items: center;
- gap: var(--gap-control);
- min-height: 24px;
- padding: 0;
+ justify-content: center;
border: none;
+ border-radius: var(--radius-lg);
background: transparent;
color: var(--text-tertiary);
- font-size: var(--type-body-6-size);
- line-height: var(--type-body-6-line-height);
- font-weight: var(--type-body-6-weight);
- transition: color var(--duration-fast) var(--ease-out);
+ transition:
+ background-color var(--duration-fast) var(--ease-out),
+ color var(--duration-fast) var(--ease-out);
}
-.workspace-sidebar-panel__tab:hover {
+.workspace-activity-bar__button:hover {
+ background: var(--bg-hover);
color: var(--text-secondary);
}
-.workspace-sidebar-panel__tab.active {
+.workspace-activity-bar__button--active {
+ background: color-mix(in srgb, var(--accent-blue) 14%, transparent);
color: var(--text-primary);
}
-.workspace-sidebar-panel__tab.active::after {
- content: "";
+.workspace-activity-bar__label {
position: absolute;
- right: 0;
- bottom: -8px;
- left: 0;
- height: 1.5px;
- border-radius: var(--radius-pill);
- background: color-mix(in srgb, var(--accent-blue) 90%, white 10%);
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+.workspace-sidebar-panel__content {
+ display: flex;
+ min-width: 0;
+ min-height: 0;
+ flex: 1;
+}
+
+.workspace-sidebar-view {
+ display: flex;
+ min-width: 0;
+ min-height: 0;
+ flex: 1;
+ flex-direction: column;
}
.workspace-sidebar-panel__actions {
@@ -12380,116 +12524,369 @@ textarea.input {
flex: 1;
}
-.workspace-sidebar-panel .panel-toolbar-btn {
- width: 24px;
- height: 24px;
- border: none;
- border-radius: 6px;
- background: transparent;
- color: var(--text-tertiary);
-}
-
-.workspace-sidebar-panel .panel-toolbar-btn:hover {
- background: var(--bg-hover);
- color: var(--text-primary);
+.workspace-sidebar-panel__body--stacked {
+ flex-direction: column;
}
-.file-tree-shell {
+.workspace-sidebar-section {
display: flex;
min-height: 0;
- flex: 1;
flex-direction: column;
+ padding: var(--sp-3) var(--sp-3) 0;
}
-.file-tree-shell .file-tree-search,
-.file-tree-shell .file-tree-search--desktop {
- display: flex;
- align-items: center;
- gap: var(--gap-default);
- margin: var(--space-default) var(--inset-control-inline);
- padding-inline: var(--inset-control-inline);
- min-height: var(--control-height-md);
- border: 1px solid color-mix(in srgb, var(--border) 84%, transparent);
- border-radius: var(--radius-panel);
- background: color-mix(in srgb, var(--bg-surface) 90%, var(--bg-page));
-}
-
-.file-tree-shell .file-tree-search:focus-within,
-.file-tree-shell .file-tree-search--desktop:focus-within {
- border-color: color-mix(in srgb, var(--accent-blue) 70%, transparent);
+.workspace-sidebar-section--fill {
+ flex: 1;
+ padding-bottom: var(--sp-3);
}
-.file-tree-shell .file-tree-search-input {
- min-height: 30px;
- font-size: var(--type-body-3-size);
- line-height: var(--type-body-3-line-height);
- font-weight: var(--type-body-3-weight);
+.workspace-sidebar-section__header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--sp-2);
+ margin-bottom: var(--sp-2);
}
-.file-tree-shell .file-tree {
- padding: 0 0 6px;
+.workspace-sidebar-section__actions {
+ margin-left: auto;
}
-.file-tree-shell .tree-item {
- position: relative;
- gap: var(--gap-tight);
- min-height: 26px;
- margin: 0 var(--gap-tight);
- padding: 3px var(--inset-control-block) 3px var(--inset-row-inline);
- border-radius: var(--radius-panel);
- color: var(--text-secondary);
- transition:
- background-color var(--duration-fast) var(--ease-out),
- color var(--duration-fast) var(--ease-out);
+.workspace-sidebar-section__title {
+ margin: 0;
+ color: var(--text-tertiary);
+ font-size: var(--type-body-6-size);
+ line-height: var(--type-body-6-line-height);
+ font-weight: var(--type-body-6-weight);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
}
-.file-tree-shell .tree-item:hover {
- background: color-mix(in srgb, var(--bg-hover) 92%, transparent);
+.workspace-open-editors {
+ display: flex;
+ flex-direction: column;
+ gap: var(--gap-micro);
}
-.file-tree-shell .tree-item.selected {
- padding-left: calc(var(--inset-row-inline) - var(--state-focus-ring-width));
- border-left: var(--state-focus-ring-width) solid var(--state-selected-border);
- background: var(--state-selected-bg);
+.workspace-open-editors__header {
+ display: flex;
+ align-items: center;
+ gap: var(--gap-compact);
+ margin-bottom: var(--sp-2);
}
-.file-tree-shell .tree-item--context-target {
- background: color-mix(in srgb, var(--accent-blue) 16%, transparent);
+.workspace-open-editors__header-main {
+ display: flex;
+ min-width: 0;
+ align-items: center;
+ gap: var(--gap-compact);
+ flex: 1 1 auto;
}
-.file-tree-shell .tree-chevron {
- width: 14px;
- height: 14px;
+.workspace-open-editors__toggle {
color: var(--text-tertiary);
}
-.file-tree-shell .tree-icon.file {
- color: var(--icon-file-default);
+.workspace-open-editors__title {
+ display: inline-flex;
+ align-items: baseline;
+ justify-content: flex-start;
+ gap: var(--gap-compact);
+ margin: 0;
+ min-width: 0;
}
-.file-tree-shell .tree-label {
- min-width: 0;
+.workspace-open-editors__title-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
- color: var(--text-primary);
- font-size: var(--type-body-3-size);
- line-height: var(--type-body-3-line-height);
- font-weight: var(--type-body-3-weight);
-}
-
-.file-tree-shell .tree-search-labels {
- gap: var(--gap-hairline);
}
-.file-tree-shell .tree-search-path,
-.file-tree-shell .tree-empty-hint,
-.file-tree-shell .tree-loading {
- color: var(--text-tertiary);
- font-family: var(--font-mono);
- font-size: var(--type-body-5-size);
- line-height: var(--type-body-5-line-height);
- font-weight: var(--type-body-5-weight);
+.workspace-open-editors__count {
+ color: var(--text-secondary);
+ font-size: var(--type-body-6-size);
+ line-height: var(--type-body-6-line-height);
+}
+
+.workspace-open-editors__close-all {
+ margin-left: auto;
+ min-height: 28px;
+ padding: 0;
+ border: none;
+ background: transparent;
+ color: var(--text-secondary);
+ font-size: var(--type-body-5-size);
+ line-height: var(--type-body-5-line-height);
+ font-weight: var(--type-body-5-weight);
+}
+
+.workspace-open-editors__close-all:hover:not(:disabled) {
+ color: var(--text-primary);
+}
+
+.workspace-open-editors__close-all:disabled {
+ color: var(--text-muted);
+}
+
+.workspace-open-editors__row {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ align-items: center;
+ gap: var(--gap-compact);
+}
+
+.workspace-open-editors__item {
+ display: block;
+ width: 100%;
+ min-width: 0;
+ min-height: 28px;
+ padding: 0 var(--sp-2);
+ border: none;
+ border-radius: var(--radius-md);
+ background: transparent;
+ overflow: hidden;
+ color: var(--text-secondary);
+ text-align: left;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.workspace-open-editors__item-label {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.workspace-open-editors__item:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.workspace-open-editors__item--active {
+ background: var(--state-selected-bg);
+ color: var(--text-primary);
+}
+
+.workspace-open-editors__item-close {
+ flex: 0 0 auto;
+ color: var(--text-tertiary);
+}
+
+.workspace-quick-jump {
+ padding-bottom: var(--sp-3);
+}
+
+.workspace-quick-jump__search {
+ display: flex;
+ align-items: center;
+ gap: var(--gap-default);
+ min-height: 34px;
+ padding: 0 calc(var(--sp-3) - var(--gap-compact));
+ border: 1px solid color-mix(in srgb, var(--border) 84%, transparent);
+ border-radius: var(--radius-lg);
+ background: color-mix(in srgb, var(--bg-surface) 92%, var(--bg-page));
+}
+
+.workspace-quick-jump__search:focus-within {
+ border-color: color-mix(in srgb, var(--accent-blue) 70%, transparent);
+}
+
+.workspace-quick-jump__input {
+ min-width: 0;
+ min-height: 32px;
+ flex: 1;
+ border: none;
+ background: transparent;
+ color: var(--text-primary);
+ font-size: var(--type-body-3-size);
+ line-height: var(--type-body-3-line-height);
+ font-weight: var(--type-body-3-weight);
+}
+
+.workspace-quick-jump__input::placeholder {
+ color: color-mix(in srgb, var(--text-tertiary) 92%, var(--bg-panel));
+}
+
+.workspace-quick-jump__input:focus {
+ outline: none;
+}
+
+.workspace-quick-jump__results {
+ display: flex;
+ flex-direction: column;
+ padding-top: var(--sp-2);
+}
+
+.workspace-quick-jump__item {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--gap-compact);
+ width: 100%;
+ min-height: 40px;
+ padding: var(--sp-2) 0;
+ border: none;
+ background: transparent;
+ color: inherit;
+ text-align: left;
+ transition:
+ background-color var(--duration-fast) var(--ease-out),
+ color var(--duration-fast) var(--ease-out);
+}
+
+.workspace-quick-jump__item:hover {
+ color: var(--text-primary);
+}
+
+.workspace-quick-jump__item:focus-visible {
+ outline: none;
+ border-radius: var(--radius-md);
+ box-shadow: inset 0 0 0 var(--state-focus-ring-width)
+ color-mix(in srgb, var(--border-focus) 64%, transparent);
+}
+
+.workspace-quick-jump__primary {
+ min-width: 0;
+ overflow: hidden;
+ color: var(--text-primary);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: var(--type-body-3-size);
+ line-height: var(--type-body-3-line-height);
+ font-weight: var(--type-body-3-weight);
+}
+
+.workspace-quick-jump__secondary {
+ min-width: 0;
+ overflow: hidden;
+ color: var(--text-tertiary);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-family: var(--font-mono);
+ font-size: var(--type-body-6-size);
+ line-height: var(--type-body-6-line-height);
+ font-weight: var(--type-body-6-weight);
+}
+
+.workspace-quick-jump__state {
+ margin: 0;
+ padding-top: var(--sp-2);
+ color: var(--text-tertiary);
+ font-size: var(--type-body-5-size);
+ line-height: var(--type-body-5-line-height);
+ font-weight: var(--type-body-5-weight);
+}
+
+.workspace-sidebar-panel .panel-toolbar-btn {
+ width: 24px;
+ height: 24px;
+ border: none;
+ border-radius: 6px;
+ background: transparent;
+ color: var(--text-tertiary);
+}
+
+.workspace-sidebar-panel .panel-toolbar-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.file-tree-shell {
+ display: flex;
+ min-height: 0;
+ flex: 1;
+ flex-direction: column;
+}
+
+.file-tree-shell .file-tree-search,
+.file-tree-shell .file-tree-search--desktop {
+ display: flex;
+ align-items: center;
+ gap: var(--gap-default);
+ margin: var(--space-default) var(--inset-control-inline);
+ padding-inline: var(--inset-control-inline);
+ min-height: var(--control-height-md);
+ border: 1px solid color-mix(in srgb, var(--border) 84%, transparent);
+ border-radius: var(--radius-panel);
+ background: color-mix(in srgb, var(--bg-surface) 90%, var(--bg-page));
+}
+
+.file-tree-shell .file-tree-search:focus-within,
+.file-tree-shell .file-tree-search--desktop:focus-within {
+ border-color: color-mix(in srgb, var(--accent-blue) 70%, transparent);
+}
+
+.file-tree-shell .file-tree-search-input {
+ min-height: 30px;
+ font-size: var(--type-body-3-size);
+ line-height: var(--type-body-3-line-height);
+ font-weight: var(--type-body-3-weight);
+}
+
+.file-tree-shell .file-tree {
+ padding: 0 0 6px;
+}
+
+.file-tree-shell .tree-item {
+ position: relative;
+ gap: var(--gap-tight);
+ min-height: 26px;
+ margin: 0 var(--gap-tight);
+ padding: 3px var(--inset-control-block) 3px var(--inset-row-inline);
+ border-radius: var(--radius-panel);
+ color: var(--text-secondary);
+ transition:
+ background-color var(--duration-fast) var(--ease-out),
+ color var(--duration-fast) var(--ease-out);
+}
+
+.file-tree-shell .tree-item:hover {
+ background: color-mix(in srgb, var(--bg-hover) 92%, transparent);
+}
+
+.file-tree-shell .tree-item.selected {
+ padding-left: calc(var(--inset-row-inline) - var(--state-focus-ring-width));
+ border-left: var(--state-focus-ring-width) solid var(--state-selected-border);
+ background: var(--state-selected-bg);
+}
+
+.file-tree-shell .tree-item--context-target {
+ background: color-mix(in srgb, var(--accent-blue) 16%, transparent);
+}
+
+.file-tree-shell .tree-chevron {
+ width: 14px;
+ height: 14px;
+ color: var(--text-tertiary);
+}
+
+.file-tree-shell .tree-icon.file {
+ color: var(--icon-file-default);
+}
+
+.file-tree-shell .tree-label {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--text-primary);
+ font-size: var(--type-body-3-size);
+ line-height: var(--type-body-3-line-height);
+ font-weight: var(--type-body-3-weight);
+}
+
+.file-tree-shell .tree-search-labels {
+ gap: var(--gap-hairline);
+}
+
+.file-tree-shell .tree-search-path,
+.file-tree-shell .tree-empty-hint,
+.file-tree-shell .tree-loading {
+ color: var(--text-tertiary);
+ font-family: var(--font-mono);
+ font-size: var(--type-body-5-size);
+ line-height: var(--type-body-5-line-height);
+ font-weight: var(--type-body-5-weight);
}
.file-tree-shell .tree-item-actions {
@@ -12522,6 +12919,232 @@ textarea.input {
opacity: 1;
}
+.workspace-search-panel__controls {
+ display: flex;
+ flex-direction: column;
+ gap: var(--sp-1);
+ padding: calc(var(--sp-2) - var(--gap-compact)) var(--sp-3) var(--sp-2);
+ border-bottom: 1px solid color-mix(in srgb, var(--border) 82%, transparent);
+ background: color-mix(in srgb, var(--bg-panel) 82%, transparent);
+}
+
+.workspace-search-panel__input {
+ min-height: 34px;
+ padding: 0 10px;
+ border: 1px solid color-mix(in srgb, var(--border) 64%, transparent);
+ border-radius: 4px;
+ background: color-mix(in srgb, var(--bg-page) 82%, var(--bg-surface) 18%);
+ color: var(--text-primary);
+ font-size: var(--type-body-3-size);
+ line-height: var(--type-body-3-line-height);
+ font-weight: var(--type-body-3-weight);
+ box-shadow: none;
+}
+
+.workspace-search-panel__input::placeholder {
+ color: color-mix(in srgb, var(--text-tertiary) 92%, var(--bg-panel));
+}
+
+.workspace-search-panel__input:focus-visible {
+ outline: none;
+ border-color: color-mix(in srgb, var(--accent-blue) 72%, transparent);
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-blue) 44%, transparent);
+}
+
+.workspace-search-panel__summary {
+ color: var(--text-secondary);
+ font-size: var(--type-body-5-size);
+ line-height: var(--type-body-5-line-height);
+ font-weight: var(--type-body-5-weight);
+}
+
+.workspace-search-panel__truncate-note {
+ color: var(--text-tertiary);
+ font-family: var(--font-mono);
+ font-size: var(--type-body-6-size);
+ line-height: var(--type-body-6-line-height);
+ font-weight: var(--type-body-6-weight);
+}
+
+.workspace-search-panel__results {
+ display: flex;
+ min-height: 0;
+ flex: 1;
+ flex-direction: column;
+ overflow-y: auto;
+ padding: var(--sp-1) 0 var(--sp-2);
+}
+
+.workspace-search-panel__state {
+ margin: 0;
+ padding: var(--sp-4) var(--sp-3);
+ color: var(--text-tertiary);
+ font-size: var(--type-body-5-size);
+ line-height: var(--type-body-5-line-height);
+ font-weight: var(--type-body-5-weight);
+}
+
+.workspace-search-panel__state-block {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: var(--gap-default);
+ padding: var(--sp-4) var(--sp-3);
+}
+
+.workspace-search-panel__group {
+ display: flex;
+ flex-direction: column;
+}
+
+.workspace-search-panel__group-header {
+ display: grid;
+ grid-template-columns: 14px minmax(0, 1fr) auto;
+ align-items: start;
+ gap: 0 var(--sp-2);
+ width: 100%;
+ padding: calc(var(--sp-2) - var(--gap-hairline)) var(--sp-3) var(--gap-tight)
+ calc(var(--sp-3) - var(--gap-compact));
+ border: none;
+ background: transparent;
+ color: var(--text-secondary);
+ text-align: left;
+ box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--border) 96%, transparent);
+ transition:
+ background-color var(--duration-fast) var(--ease-out),
+ color var(--duration-fast) var(--ease-out);
+}
+
+.workspace-search-panel__group-header:hover {
+ background: color-mix(in srgb, var(--bg-hover) 88%, transparent);
+}
+
+.workspace-search-panel__group-header:focus-visible {
+ outline: none;
+ background: color-mix(in srgb, var(--state-selected-bg) 76%, transparent);
+ box-shadow:
+ inset 0 0 0 var(--state-focus-ring-width)
+ color-mix(in srgb, var(--border-focus) 64%, transparent),
+ inset 0 -1px 0 color-mix(in srgb, var(--border) 96%, transparent);
+}
+
+.workspace-search-panel__group-chevron {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 18px;
+ color: var(--text-tertiary);
+}
+
+.workspace-search-panel__group-copy {
+ display: flex;
+ min-width: 0;
+ flex-direction: column;
+ gap: var(--gap-compact);
+}
+
+.workspace-search-panel__group-name {
+ min-width: 0;
+ overflow: hidden;
+ color: var(--text-primary);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: var(--type-body-3-size);
+ line-height: var(--type-body-3-line-height);
+ font-weight: var(--type-body-3-weight);
+}
+
+.workspace-search-panel__group-path {
+ min-width: 0;
+ overflow: hidden;
+ color: var(--text-tertiary);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-family: var(--font-mono);
+ font-size: var(--type-body-6-size);
+ line-height: var(--type-body-6-line-height);
+ font-weight: var(--type-body-6-weight);
+}
+
+.workspace-search-panel__group-count {
+ align-self: start;
+ padding-top: var(--gap-hairline);
+ color: var(--text-tertiary);
+ font-family: var(--font-mono);
+ font-size: var(--type-body-6-size);
+ line-height: var(--type-body-6-line-height);
+ font-weight: var(--type-body-6-weight);
+}
+
+.workspace-search-panel__matches {
+ display: flex;
+ flex-direction: column;
+ padding-bottom: var(--sp-1);
+}
+
+.workspace-search-panel__match {
+ display: grid;
+ grid-template-columns: 40px minmax(0, 1fr);
+ align-items: baseline;
+ gap: var(--sp-2);
+ width: 100%;
+ padding: var(--sp-1) var(--sp-3) var(--sp-1) calc(var(--control-height-md) + var(--gap-compact));
+ border: none;
+ background: transparent;
+ color: var(--text-primary);
+ text-align: left;
+ transition:
+ background-color var(--duration-fast) var(--ease-out),
+ color var(--duration-fast) var(--ease-out);
+}
+
+.workspace-search-panel__match:hover {
+ background: color-mix(in srgb, var(--bg-hover) 88%, transparent);
+}
+
+.workspace-search-panel__match:focus-visible {
+ outline: none;
+ background: color-mix(in srgb, var(--state-selected-bg) 76%, transparent);
+ box-shadow: inset 0 0 0 var(--state-focus-ring-width)
+ color-mix(in srgb, var(--border-focus) 64%, transparent);
+}
+
+.workspace-search-panel__line {
+ color: var(--text-tertiary);
+ text-align: right;
+ font-family: var(--font-mono);
+ font-size: var(--type-body-6-size);
+ line-height: var(--type-body-6-line-height);
+ font-weight: var(--type-body-6-weight);
+}
+
+.workspace-search-panel__preview {
+ min-width: 0;
+ overflow: hidden;
+ color: var(--text-primary);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: var(--type-body-3-size);
+ line-height: var(--type-body-3-line-height);
+ font-weight: var(--type-body-3-weight);
+}
+
+.workspace-search-panel__preview mark {
+ padding: 0;
+ border-radius: 2px;
+ background: color-mix(in srgb, var(--accent-blue) 18%, transparent);
+ color: inherit;
+}
+
+.workspace-search-panel--mobile {
+ background: transparent;
+}
+
+.workspace-search-panel--mobile .workspace-search-panel__controls {
+ padding-top: 0;
+ background: transparent;
+}
+
.mobile-sheet--files .file-tree-shell--mobile .file-tree-search {
margin: 0;
border-right: none;
@@ -13142,8 +13765,8 @@ textarea.input {
position: relative;
display: inline-flex;
align-items: center;
- justify-content: flex-start;
- gap: 6px;
+ justify-content: center;
+ min-width: 32px;
min-height: 32px;
padding: 0;
border: none;
@@ -13156,6 +13779,12 @@ textarea.input {
transition: color var(--duration-fast) var(--ease-out);
}
+.mobile-files-sheet__segment-icon {
+ display: block;
+ line-height: 0;
+ flex-shrink: 0;
+}
+
.mobile-files-sheet__segment:hover {
color: var(--text-secondary);
}
@@ -13176,35 +13805,6 @@ textarea.input {
background: color-mix(in srgb, var(--accent-blue) 90%, white 10%);
}
-.mobile-files-sheet__tab-actions {
- display: inline-flex;
- align-items: center;
- gap: 4px;
- flex-shrink: 0;
- margin-left: auto;
-}
-
-.mobile-files-sheet__tab-action {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- padding: 0;
- border: none;
- border-radius: 6px;
- background: transparent;
- color: var(--text-tertiary);
- transition:
- background-color var(--duration-fast) var(--ease-out),
- color var(--duration-fast) var(--ease-out);
-}
-
-.mobile-files-sheet__tab-action:hover {
- background: color-mix(in srgb, var(--bg-hover) 82%, var(--accent-blue) 10%);
- color: var(--text-primary);
-}
-
.mobile-files-sheet__content,
.mobile-files-sheet__detail {
display: flex;
@@ -13220,6 +13820,13 @@ textarea.input {
flex: 1;
}
+.mobile-explorer-panel {
+ display: flex;
+ min-height: 0;
+ flex: 1;
+ flex-direction: column;
+}
+
.workspace-page > .workspace-status-bar {
width: 100%;
}
diff --git a/packages/web/src/styles/components.theme.test.ts b/packages/web/src/styles/components.theme.test.ts
index c774e503..0358c3f0 100644
--- a/packages/web/src/styles/components.theme.test.ts
+++ b/packages/web/src/styles/components.theme.test.ts
@@ -893,9 +893,10 @@ describe("components.css theme-sensitive surfaces", () => {
const statusBar = getLastRuleBlock(".workspace-status-bar");
const agentPanes = getLastRuleBlock(".workspace-main-stage > .agent-panes");
const bottomPanel = getLastRuleBlock(".workspace-bottom-panel");
- const sidebarTabs = getLastRuleBlock(".workspace-sidebar-panel__tabs");
- const sidebarTab = getLastRuleBlock(".workspace-sidebar-panel__tab");
- const sidebarTabActiveAfter = getLastRuleBlock(".workspace-sidebar-panel__tab.active::after");
+ const activityBar = getLastRuleBlock(".workspace-activity-bar");
+ const activityBarButton = getLastRuleBlock(".workspace-activity-bar__button");
+ const activityBarButtonHover = getLastRuleBlock(".workspace-activity-bar__button:hover");
+ const activityBarButtonActive = getLastRuleBlock(".workspace-activity-bar__button--active");
const sidebarActions = getLastRuleBlock(".workspace-sidebar-panel__actions");
const verticalDividerRules = getRuleBlocksFrom(stylesheet, ".split-divider-v").join("\n");
const horizontalDividerRules = getRuleBlocksFrom(stylesheet, ".split-divider-h").join("\n");
@@ -989,9 +990,16 @@ describe("components.css theme-sensitive surfaces", () => {
);
expect(resolvingStrongLine).toContain("border: 1px solid var(--state-info-border)");
expect(resolvingStrongLine).toContain("background: var(--state-info-bg)");
- expect(sidebarTabs).toContain("gap: var(--gap-default)");
- expect(sidebarTab).toContain("gap: var(--gap-control)");
- expect(sidebarTabActiveAfter).toContain("border-radius: var(--radius-pill)");
+ expect(activityBar).toContain("border-right: 1px solid var(--border)");
+ expect(activityBar).toContain(
+ "background: color-mix(in srgb, var(--bg-panel) 88%, var(--bg-page))"
+ );
+ expect(activityBarButton).toContain("border-radius: var(--radius-lg)");
+ expect(activityBarButton).toContain("background: transparent");
+ expect(activityBarButtonHover).toContain("background: var(--bg-hover)");
+ expect(activityBarButtonActive).toContain(
+ "background: color-mix(in srgb, var(--accent-blue) 14%, transparent)"
+ );
expect(sidebarActions).toContain("gap: var(--gap-control)");
expect(verticalDividerRules).toContain("width: 10px");
expect(verticalDividerRules).not.toContain("width: 8px");
@@ -2138,11 +2146,17 @@ describe("components.css theme-sensitive surfaces", () => {
const mobileFilesGitSurface = getLastRuleBlock(".mobile-sheet--files .git-panel--mobile");
const mobileFilesSegmented = getLastRuleBlock(".mobile-files-sheet__segmented");
const mobileFilesSegment = getLastRuleBlock(".mobile-files-sheet__segment");
+ const mobileFilesSegmentIcon = getLastRuleBlock(".mobile-files-sheet__segment-icon");
const mobileFilesSegmentActive = getLastRuleBlock(".mobile-files-sheet__segment.active");
const mobileFilesSegmentIndicator = getLastRuleBlock(
".mobile-files-sheet__segment.active::after"
);
- const mobileFilesTabAction = getLastRuleBlock(".mobile-files-sheet__tab-action");
+ const workspaceSectionHeader = getLastRuleBlock(".workspace-sidebar-section__header");
+ const workspaceSectionActions = getLastRuleBlock(".workspace-sidebar-section__actions");
+ const mobileExplorerPanel = getLastRuleBlock(".mobile-explorer-panel");
+ const mobileQuickJumpSearch = getLastRuleBlock(".workspace-quick-jump__search");
+ const mobileQuickJumpItem = getLastRuleBlock(".workspace-quick-jump__item");
+ const mobileSearchPanel = getLastRuleBlock(".workspace-search-panel--mobile");
const mobileFileSearch = getLastRuleBlock(
".mobile-sheet--files .file-tree-shell--mobile .file-tree-search"
);
@@ -2175,13 +2189,21 @@ describe("components.css theme-sensitive surfaces", () => {
expect(mobileFilesSegmented).toContain("border-radius: 0");
expect(mobileFilesSegmented).not.toContain("linear-gradient(");
expect(mobileFilesSegmented).toContain("box-shadow: none");
+ expect(mobileExplorerPanel).toContain("display: flex");
+ expect(mobileExplorerPanel).toContain("flex-direction: column");
expect(mobileFilesSegment).toContain("padding: 0");
+ expect(mobileFilesSegment).toContain("justify-content: center");
+ expect(mobileFilesSegment).toContain("min-width: 32px");
expect(mobileFilesSegment).toContain("font-weight: var(--type-body-6-weight)");
+ expect(mobileFilesSegmentIcon).toContain("display: block");
expect(mobileFilesSegmentActive).toContain("background: transparent");
expect(mobileFilesSegmentIndicator).toContain("height: 1.5px");
- expect(mobileFilesTabAction).toContain("border: none");
- expect(mobileFilesTabAction).toContain("border-radius: 6px");
- expect(mobileFilesTabAction).toContain("background: transparent");
+ expect(workspaceSectionHeader).toContain("justify-content: space-between");
+ expect(workspaceSectionHeader).toContain("margin-bottom: var(--sp-2)");
+ expect(workspaceSectionActions).toContain("margin-left: auto");
+ expect(mobileQuickJumpSearch).toContain("border: 1px solid");
+ expect(mobileQuickJumpItem).toContain("grid-template-columns: minmax(0, 1fr)");
+ expect(mobileSearchPanel).toContain("background: transparent");
expect(mobileFilesSurface).toContain(
"border: 1px solid color-mix(in srgb, var(--border) 80%, transparent)"
);
@@ -2609,6 +2631,81 @@ describe("components.css theme-sensitive surfaces", () => {
expect(rowActionsDesktop).toContain("opacity: 0");
});
+ it("keeps workspace search and quick open on compact editor-search chrome", () => {
+ const openEditorsHeader = getLastRuleBlock(".workspace-open-editors__header");
+ const openEditorsHeaderMain = getLastRuleBlock(".workspace-open-editors__header-main");
+ const openEditorsTitle = getLastRuleBlock(".workspace-open-editors__title");
+ const openEditorsTitleText = getLastRuleBlock(".workspace-open-editors__title-text");
+ const openEditorsCloseAll = getLastRuleBlock(".workspace-open-editors__close-all");
+ const openEditorsRow = getLastRuleBlock(".workspace-open-editors__row");
+ const searchControls = getLastRuleBlock(".workspace-search-panel__controls");
+ const searchInput = getLastRuleBlock(".workspace-search-panel__input");
+ const openEditorsItem = getLastRuleBlock(".workspace-open-editors__item");
+ const openEditorsItemLabel = getLastRuleBlock(".workspace-open-editors__item-label");
+ const searchGroupHeader = getLastRuleBlock(".workspace-search-panel__group-header");
+ const searchGroupPath = getLastRuleBlock(".workspace-search-panel__group-path");
+ const searchMatch = getLastRuleBlock(".workspace-search-panel__match");
+ const searchLine = getLastRuleBlock(".workspace-search-panel__line");
+ const quickOpen = getLastRuleBlock(".quick-open");
+ const quickOpenSearch = getLastRuleBlock(".quick-open__search");
+ const quickOpenItem = getLastRuleBlock(".quick-open__item");
+ const quickOpenItemActive = getLastRuleBlock(".quick-open__item--active");
+ const quickOpenItemSelected = getLastRuleBlock('.quick-open__item[aria-selected="true"]');
+ const quickOpenPrimary = getLastRuleBlock(".quick-open__primary");
+ const quickOpenSecondary = getLastRuleBlock(".quick-open__secondary");
+ const quickOpenSelectedSecondary = getLastRuleBlock(
+ '.quick-open__item[aria-selected="true"] .quick-open__secondary'
+ );
+
+ expect(openEditorsHeader).toContain("display: flex");
+ expect(openEditorsHeaderMain).toContain("flex: 1 1 auto");
+ expect(openEditorsHeaderMain).toContain("min-width: 0");
+ expect(openEditorsTitle).toContain("justify-content: flex-start");
+ expect(openEditorsTitle).toContain("min-width: 0");
+ expect(openEditorsTitleText).toContain("text-overflow: ellipsis");
+ expect(openEditorsTitleText).toContain("white-space: nowrap");
+ expect(openEditorsCloseAll).toContain("margin-left: auto");
+ expect(openEditorsCloseAll).toContain("background: transparent");
+ expect(openEditorsCloseAll).toContain("color: var(--text-secondary)");
+ expect(openEditorsRow).toContain("grid-template-columns: minmax(0, 1fr) auto");
+ expect(searchControls).toContain("border-bottom: 1px solid color-mix(");
+ expect(searchControls).toContain("background: color-mix(");
+ expect(searchInput).toContain("min-height: 34px");
+ expect(searchInput).toContain("border-radius: 4px");
+ expect(searchInput).toContain("box-shadow: none");
+ expect(openEditorsItem).toContain("overflow: hidden");
+ expect(openEditorsItem).toContain("text-overflow: ellipsis");
+ expect(openEditorsItem).toContain("white-space: nowrap");
+ expect(openEditorsItemLabel).toContain("text-overflow: ellipsis");
+ expect(openEditorsItemLabel).toContain("white-space: nowrap");
+ expect(searchGroupHeader).toContain("grid-template-columns: 14px minmax(0, 1fr) auto");
+ expect(searchGroupHeader).toContain("box-shadow: inset 0 -1px 0 color-mix(");
+ expect(searchGroupPath).toContain("font-family: var(--font-mono)");
+ expect(searchGroupPath).toContain("font-size: var(--type-body-6-size)");
+ expect(searchMatch).toContain("grid-template-columns: 40px minmax(0, 1fr)");
+ expect(searchLine).toContain("text-align: right");
+
+ expect(quickOpen).toContain("border: 1px solid var(--surface-overlay-border)");
+ expect(quickOpen).toContain("border-radius: var(--radius-overlay)");
+ expect(quickOpen).toContain("background: var(--surface-overlay-bg)");
+ expect(quickOpenSearch).toContain("border-bottom: 1px solid color-mix(");
+ expect(quickOpenSearch).toContain("background: color-mix(");
+ expect(quickOpenItem).toContain("gap: var(--gap-compact)");
+ expect(quickOpenItem).toContain("box-shadow: inset 0 -1px 0 color-mix(");
+ expect(quickOpenItemActive).toContain(
+ "background: color-mix(in srgb, var(--accent-blue) 12%, var(--bg-panel))"
+ );
+ expect(quickOpenItemSelected).toContain(
+ "background: color-mix(in srgb, var(--accent-blue) 12%, var(--bg-panel))"
+ );
+ expect(quickOpenPrimary).toContain("font-size: var(--type-body-3-size)");
+ expect(quickOpenSecondary).toContain("font-size: var(--type-body-5-size)");
+ expect(quickOpenSecondary).toContain("color: var(--text-secondary)");
+ expect(quickOpenSelectedSecondary).toContain(
+ "color: color-mix(in srgb, var(--accent-blue) 52%, var(--text-secondary))"
+ );
+ });
+
it("keeps the desktop git panel and command palette on tighter tool-surface chrome", () => {
const gitScroll = getLastRuleBlock(".git-panel-scroll");
const gitCommitBlock = getLastRuleBlock(".git-commit-block");
diff --git a/packages/web/src/ui-preview/scenes/showcase-scenes.tsx b/packages/web/src/ui-preview/scenes/showcase-scenes.tsx
index ec305fdd..c6323492 100644
--- a/packages/web/src/ui-preview/scenes/showcase-scenes.tsx
+++ b/packages/web/src/ui-preview/scenes/showcase-scenes.tsx
@@ -1284,7 +1284,7 @@ export function createShowcaseScenes(): UiPreviewSceneDefinition[] {
}),
render: () => (
}
/>