Skip to content

Commit 4f9a705

Browse files
committed
fix(web): restore workspace tab and file tree visuals
1 parent 08c13ec commit 4f9a705

6 files changed

Lines changed: 185 additions & 8 deletions

File tree

packages/web/src/features/workspace/index.test.tsx

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,11 @@ describe("WorkspacePage", () => {
244244
expect(branchButton).not.toBeNull();
245245
fireEvent.click(branchButton as HTMLElement);
246246

247+
const gitTab = screen.getByRole("tab", { name: "Git" });
247248
expect(screen.getByRole("tablist", { name: "Workspace sections" })).toBeInTheDocument();
248-
expect(screen.getByRole("tab", { name: "Git" })).toHaveAttribute("aria-selected", "true");
249-
expect(screen.getByRole("tab", { name: "Git" })).toHaveClass("panel-tab", "active");
249+
expect(gitTab).toHaveAttribute("aria-selected", "true");
250+
expect(gitTab).toHaveClass("workspace-sidebar-panel__tab", "active");
251+
expect(gitTab).not.toHaveClass("panel-tab");
250252
expect(
251253
await screen.findByPlaceholderText("Search branches or create new branch...")
252254
).toBeInTheDocument();
@@ -257,6 +259,62 @@ describe("WorkspacePage", () => {
257259
});
258260
});
259261

262+
it("uses workspace sidebar-specific tab styling without legacy panel-tab classes", async () => {
263+
const sendCommand = vi.fn().mockImplementation(async (op: string) => {
264+
if (op === "git.status") {
265+
return {
266+
branch: "main",
267+
ahead: 0,
268+
behind: 0,
269+
staged: [],
270+
modified: [],
271+
deleted: [],
272+
untracked: [],
273+
};
274+
}
275+
276+
return [];
277+
});
278+
279+
const store = createStore();
280+
store.set(connectionStatusAtom, "connected");
281+
store.set(wsClientAtom, { sendCommand } as never);
282+
seedReadyWorkspaceState(store, {
283+
"ws-test": {
284+
id: "ws-test",
285+
path: "/home/spencer/workspace/coder-studio",
286+
targetRuntime: "native",
287+
openedAt: 1,
288+
lastActiveAt: 1,
289+
uiState: {
290+
leftPanelWidth: 280,
291+
bottomPanelHeight: 200,
292+
focusMode: false,
293+
},
294+
},
295+
});
296+
297+
render(
298+
<Provider store={store}>
299+
<MemoryRouter initialEntries={["/workspace"]}>
300+
<Routes>
301+
<Route path="/workspace" element={<WorkspaceDesktopView />} />
302+
</Routes>
303+
</MemoryRouter>
304+
</Provider>
305+
);
306+
307+
await screen.findByTestId("file-tree-panel");
308+
309+
const filesTab = screen.getByRole("tab", { name: /Files|/i });
310+
const gitTab = screen.getByRole("tab", { name: "Git" });
311+
312+
expect(filesTab).toHaveClass("workspace-sidebar-panel__tab", "active");
313+
expect(filesTab).not.toHaveClass("panel-tab");
314+
expect(gitTab).toHaveClass("workspace-sidebar-panel__tab");
315+
expect(gitTab).not.toHaveClass("panel-tab");
316+
});
317+
260318
it("does not render a duplicate worktree entry button in the desktop git header", async () => {
261319
const sendCommand = vi.fn().mockImplementation(async (op: string) => {
262320
if (op === "git.status") {

packages/web/src/features/workspace/views/desktop/workspace-desktop-view.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,10 @@ export const WorkspaceDesktopView: FC = () => {
114114
value={sidebarTab}
115115
>
116116
<TabList className="workspace-sidebar-panel__tabs">
117-
<Tab className="workspace-sidebar-panel__tab panel-tab" value="files">
117+
<Tab className="workspace-sidebar-panel__tab" value="files">
118118
<span>{t("file.title")}</span>
119119
</Tab>
120-
<Tab className="workspace-sidebar-panel__tab panel-tab" value="git">
120+
<Tab className="workspace-sidebar-panel__tab" value="git">
121121
<span>{t("label.git")}</span>
122122
</Tab>
123123
</TabList>

packages/web/src/features/workspace/views/mobile/mobile-files-sheet.test.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,20 @@ describe("MobileFilesSheet", () => {
7070
).toBeTruthy();
7171
expect(searchInput).toBeInTheDocument();
7272
});
73+
74+
it("uses the mobile segmented tab styling without legacy panel-tab classes", () => {
75+
render(
76+
<Provider store={createStore()}>
77+
<MobileFilesSheet workspaceId="ws-test" route={{ kind: "root" }} activeTab="git" />
78+
</Provider>
79+
);
80+
81+
const filesTab = screen.getByRole("tab", { name: "Files" });
82+
const gitTab = screen.getByRole("tab", { name: "Git" });
83+
84+
expect(filesTab).toHaveClass("mobile-files-sheet__segment");
85+
expect(filesTab).not.toHaveClass("panel-tab");
86+
expect(gitTab).toHaveClass("mobile-files-sheet__segment", "active");
87+
expect(gitTab).not.toHaveClass("panel-tab");
88+
});
7389
});

packages/web/src/features/workspace/views/mobile/mobile-files-sheet.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ export function MobileFilesSheet({
8888
value={activeTab}
8989
>
9090
<TabList className="mobile-files-sheet__tabs">
91-
<Tab className="mobile-files-sheet__segment panel-tab" value="files">
91+
<Tab className="mobile-files-sheet__segment" value="files">
9292
<span>{t("file.title")}</span>
9393
</Tab>
94-
<Tab className="mobile-files-sheet__segment panel-tab" value="git">
94+
<Tab className="mobile-files-sheet__segment" value="git">
9595
<span>{t("label.git")}</span>
9696
</Tab>
9797
</TabList>

packages/web/src/features/workspace/views/shared/file-tree-panel.test.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,77 @@ describe("FileTreePanel", () => {
339339
});
340340
});
341341

342+
it("restores per-type icon tone classes for tree rows and search results", async () => {
343+
const searchFiles = [
344+
{ path: "src/app.tsx", name: "app.tsx", kind: "file" },
345+
{ path: "src/config.json", name: "config.json", kind: "file" },
346+
{ path: "src/README.md", name: "README.md", kind: "file" },
347+
{ path: "src/logo.svg", name: "logo.svg", kind: "file" },
348+
{ path: "src/notes.bin", name: "notes.bin", kind: "file" },
349+
];
350+
const sendCommand = vi.fn().mockImplementation(async (op: string, args: { query?: string }) => {
351+
if (op === "file.search") {
352+
const query = args.query?.toLowerCase() ?? "";
353+
return {
354+
files: searchFiles.filter((item) => item.name.toLowerCase().includes(query)),
355+
};
356+
}
357+
358+
return { ok: true };
359+
});
360+
const store = createStore();
361+
store.set(wsClientAtom, { sendCommand } as never);
362+
store.set(
363+
fileTreeAtomFamily("ws-test"),
364+
new Map([
365+
[
366+
".",
367+
[
368+
{
369+
path: "src",
370+
name: "src",
371+
kind: "dir",
372+
children: [
373+
{ path: "src/app.tsx", name: "app.tsx", kind: "file" },
374+
{ path: "src/config.json", name: "config.json", kind: "file" },
375+
{ path: "src/README.md", name: "README.md", kind: "file" },
376+
{ path: "src/logo.svg", name: "logo.svg", kind: "file" },
377+
{ path: "src/notes.bin", name: "notes.bin", kind: "file" },
378+
],
379+
},
380+
],
381+
],
382+
])
383+
);
384+
385+
render(
386+
<Provider store={store}>
387+
<FileTreePanel workspaceId="ws-test" />
388+
</Provider>
389+
);
390+
391+
const folderLabel = screen.getByText("src");
392+
expect(folderLabel.previousElementSibling).toHaveClass("tree-icon", "folder");
393+
394+
await screen.findByText("app.tsx");
395+
396+
expect(screen.getByText("app.tsx").previousElementSibling).toHaveClass("tree-icon", "code");
397+
expect(screen.getByText("config.json").previousElementSibling).toHaveClass("tree-icon", "data");
398+
expect(screen.getByText("README.md").previousElementSibling).toHaveClass("tree-icon", "doc");
399+
expect(screen.getByText("logo.svg").previousElementSibling).toHaveClass("tree-icon", "media");
400+
expect(screen.getByText("notes.bin").previousElementSibling).toHaveClass("tree-icon", "file");
401+
402+
fireEvent.change(screen.getByPlaceholderText("action.search_files"), {
403+
target: { value: "logo" },
404+
});
405+
406+
const searchLabel = await screen.findByText("logo.svg");
407+
expect(searchLabel.closest(".tree-search-labels")?.previousElementSibling).toHaveClass(
408+
"tree-icon",
409+
"media"
410+
);
411+
});
412+
342413
it("opens the new file dialog from the toolbar and dispatches file.create", async () => {
343414
const sendCommand = vi
344415
.fn()

packages/web/src/features/workspace/views/shared/file-tree-panel.tsx

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -689,8 +689,40 @@ function getNodeIcon(node: FileNode, isExpanded: boolean): LucideIcon {
689689

690690
function getNodeToneClass(node: FileNode, isExpanded: boolean) {
691691
if (node.kind === "dir") {
692-
return isExpanded ? "folder-open" : "folder";
692+
return "folder";
693693
}
694694

695-
return "file";
695+
const ext = node.name.split(".").pop()?.toLowerCase();
696+
697+
switch (ext) {
698+
case "ts":
699+
case "tsx":
700+
case "js":
701+
case "jsx":
702+
case "mjs":
703+
case "cjs":
704+
case "py":
705+
case "go":
706+
case "rs":
707+
case "java":
708+
return "code";
709+
case "json":
710+
case "yaml":
711+
case "yml":
712+
case "toml":
713+
case "lock":
714+
return "data";
715+
case "md":
716+
case "txt":
717+
return "doc";
718+
case "png":
719+
case "jpg":
720+
case "jpeg":
721+
case "gif":
722+
case "svg":
723+
case "webp":
724+
return "media";
725+
default:
726+
return "file";
727+
}
696728
}

0 commit comments

Comments
 (0)