From 91a4c7bc327e2f90f19aad383518899431d16752 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:23:18 +0800 Subject: [PATCH] feat(app): hide project names in tabs by default (#46778) --- packages/app/e2e/regression/mobile-shell.spec.ts | 2 +- .../e2e/regression/tab-navigate-mousedown.spec.ts | 13 +++++++++++-- packages/app/src/runtime/i18n/en.ts | 2 ++ .../app/src/settings/appearance/appearance.tsx | 15 +++++++++++++++ packages/app/src/settings/general/controllers.ts | 4 ++++ packages/app/src/settings/model.test.ts | 12 ++++++++++-- packages/app/src/settings/model.tsx | 10 +++++++++- packages/app/src/shell/titlebar/tab-nav.css | 4 ++-- packages/app/src/shell/titlebar/tab-nav.tsx | 4 +++- 9 files changed, 57 insertions(+), 9 deletions(-) diff --git a/packages/app/e2e/regression/mobile-shell.spec.ts b/packages/app/e2e/regression/mobile-shell.spec.ts index a7b925fa714..be968c85b26 100644 --- a/packages/app/e2e/regression/mobile-shell.spec.ts +++ b/packages/app/e2e/regression/mobile-shell.spec.ts @@ -42,7 +42,7 @@ test("mobile project selection and drawer navigation preserve session identity", const drawer = page.locator('[data-slot="mobile-tabs-drawer"]') await trigger.click() await expect(trigger).toHaveAttribute("aria-expanded", "true") - await expect(drawer.locator('[data-slot="tab-project"]')).toHaveText([fixture.project.name, fixture.project.name]) + await expect(drawer.locator('[data-slot="tab-project"]')).toHaveCount(0) const settings = drawer.getByRole("button", { name: "Settings", exact: true }) const help = drawer.getByRole("button", { name: "Help", exact: true }) await expect(settings).toBeVisible() diff --git a/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts index 5c4afa552f8..1ab53b8515a 100644 --- a/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts +++ b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts @@ -149,7 +149,7 @@ test("vertical tabs show project details, resize, and navigate", async ({ page } ({ server, sessionA, sessionB }) => { localStorage.setItem( "settings.v3", - JSON.stringify({ appearance: { tabLayout: "vertical" }, general: { showStatus: true } }), + JSON.stringify({ appearance: { tabLayout: "vertical", showProjectName: true }, general: { showStatus: true } }), ) localStorage.setItem( "opencode.window.browser.dat:tabs", @@ -219,7 +219,7 @@ test("vertical tabs show project details, resize, and navigate", async ({ page } await expect(tabB).toBeVisible() }) -test("appearance experimental setting switches tab orientation", async ({ page }) => { +test("appearance experimental settings control vertical tab details", async ({ page }) => { await mockServer(page) await page.addInitScript( ({ server, sessionA }) => { @@ -251,6 +251,12 @@ test("appearance experimental setting switches tab orientation", async ({ page } await expect(layout).toContainText("Vertical") await expect(page.locator('[data-slot="vertical-tabs-sidebar"]')).toBeVisible() await expect(page.locator('[data-slot="titlebar-tabs"]')).toHaveCount(0) + const projectNames = page.locator('[data-slot="vertical-tabs-sidebar"] [data-slot="tab-project"]') + await expect(projectNames).toHaveCount(0) + const projectNameSwitch = settings.getByRole("switch", { name: "Show project names", exact: true }) + await settings.locator('[data-action="settings-show-project-name"] [data-slot="switch-control"]').click() + await expect(projectNameSwitch).toBeChecked() + await expect(projectNames).toHaveText(["tab-project"]) await expect(settings.getByRole("tablist")).toHaveCSS("width", "240px") await page.setViewportSize({ width: 920, height: 720 }) @@ -274,6 +280,9 @@ test("appearance experimental setting switches tab orientation", async ({ page } await page.reload() const href = `/server/${base64Encode(server)}/session/${sessionA.id}` await page.getByRole("button", { name: "Tabs", exact: true }).click() + await expect(page.locator('[data-slot="mobile-tabs-drawer"] [data-slot="tab-project"]')).toHaveText([ + "tab-project", + ]) await expect( page .locator('[data-slot="mobile-tabs-drawer"]') diff --git a/packages/app/src/runtime/i18n/en.ts b/packages/app/src/runtime/i18n/en.ts index ce520f60d78..fe644ac5f13 100644 --- a/packages/app/src/runtime/i18n/en.ts +++ b/packages/app/src/runtime/i18n/en.ts @@ -911,6 +911,8 @@ export const dict = { "settings.appearance.row.tabs.description": "Choose how session tabs are arranged", "settings.appearance.row.tabs.horizontal": "Horizontal", "settings.appearance.row.tabs.vertical": "Vertical", + "settings.appearance.row.projectName.title": "Show project names", + "settings.appearance.row.projectName.description": "Show project names in vertical tabs and the mobile tab drawer", "settings.notifications.description": "Choose when to receive notifications and hear sounds", "settings.shortcuts.description": "Customize shortcuts for common actions", "settings.servers.description": "Manage server connections", diff --git a/packages/app/src/settings/appearance/appearance.tsx b/packages/app/src/settings/appearance/appearance.tsx index a4325525af5..1f5048e3f56 100644 --- a/packages/app/src/settings/appearance/appearance.tsx +++ b/packages/app/src/settings/appearance/appearance.tsx @@ -1,6 +1,7 @@ import { Component, createMemo } from "solid-js" import { Select } from "@opencode-ai/ui/select" import { TextInput } from "@opencode-ai/ui/text-input" +import { Switch } from "@opencode-ai/ui/switch" import { useLanguage } from "@/runtime/i18n/language" import { ExternalLink } from "@/runtime/platform/external-link" import { SettingsList } from "@/settings/list" @@ -149,6 +150,20 @@ export const SettingsAppearance: Component = () => { onSelect={(option) => option && appearance.tabs.select(option)} /> + +
+ + {language.t("settings.appearance.row.projectName.title")} + +
+
diff --git a/packages/app/src/settings/general/controllers.ts b/packages/app/src/settings/general/controllers.ts index 2c314ebf0d0..a1973ed4cde 100644 --- a/packages/app/src/settings/general/controllers.ts +++ b/packages/app/src/settings/general/controllers.ts @@ -85,6 +85,10 @@ export function createAppearanceSettingsController() { current: settings.appearance.tabLayout, select: settings.appearance.setTabLayout, }, + projectName: { + current: settings.appearance.showProjectName, + set: settings.appearance.setShowProjectName, + }, } } diff --git a/packages/app/src/settings/model.test.ts b/packages/app/src/settings/model.test.ts index e62cf864b40..a3244a4e94a 100644 --- a/packages/app/src/settings/model.test.ts +++ b/packages/app/src/settings/model.test.ts @@ -101,7 +101,14 @@ describe("settings schema", () => { terminalPlacement: "side", followUpBehavior: "steer", }, - appearance: { fontSize: 14, mono: "", sans: "", terminal: "", tabLayout: "horizontal" }, + appearance: { + fontSize: 14, + mono: "", + sans: "", + terminal: "", + tabLayout: "horizontal", + showProjectName: false, + }, keybinds: {}, permissions: { autoApprove: false }, workspaces: { defaultDestination: "last-used", lastUsed: {} }, @@ -126,7 +133,7 @@ describe("settings schema", () => { reasoningMode: 3, followUpBehavior: "invalid", }, - appearance: { fontSize: "large", mono: "Custom Mono", tabLayout: "vertical" }, + appearance: { fontSize: "large", mono: "Custom Mono", tabLayout: "vertical", showProjectName: true }, permissions: { autoApprove: true }, workspaces: { defaultDestination: "new", lastUsed: { good: "workspace", bad: true } }, keybinds: { good: "ctrl+k", bad: 3 }, @@ -146,6 +153,7 @@ describe("settings schema", () => { sans: "", terminal: "", tabLayout: "vertical", + showProjectName: true, }) expect(settings.permissions.autoApprove).toBe(true) expect(settings.workspaces).toEqual({ defaultDestination: "new", lastUsed: { good: "workspace" } }) diff --git a/packages/app/src/settings/model.tsx b/packages/app/src/settings/model.tsx index 6212b692fc3..0a0d1d2697b 100644 --- a/packages/app/src/settings/model.tsx +++ b/packages/app/src/settings/model.tsx @@ -95,6 +95,7 @@ const appearanceSchema = Persistence.struct({ sans: Schema.String, terminal: Schema.String, tabLayout: Schema.Literals(["horizontal", "vertical"]), + showProjectName: Schema.Boolean, }) const permissionsSchema = Persistence.struct({ @@ -179,7 +180,7 @@ export const defaultSettings: Settings = { terminalPlacement: "side", followUpBehavior: "steer", }, - appearance: { fontSize: 14, mono: "", sans: "", terminal: "", tabLayout: "horizontal" }, + appearance: { fontSize: 14, mono: "", sans: "", terminal: "", tabLayout: "horizontal", showProjectName: false }, keybinds: {}, permissions: { autoApprove: false }, workspaces: { defaultDestination: "last-used", lastUsed: {} }, @@ -327,6 +328,13 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont setTabLayout(value: TabLayout) { setStore("appearance", "tabLayout", value) }, + showProjectName: withFallback( + () => store.appearance?.showProjectName, + defaultSettings.appearance.showProjectName, + ), + setShowProjectName(value: boolean) { + setStore("appearance", "showProjectName", value) + }, }, keybinds: { get: (action: string) => store.keybinds?.[action], diff --git a/packages/app/src/shell/titlebar/tab-nav.css b/packages/app/src/shell/titlebar/tab-nav.css index 0a9265a9888..1cea5e4e871 100644 --- a/packages/app/src/shell/titlebar/tab-nav.css +++ b/packages/app/src/shell/titlebar/tab-nav.css @@ -15,11 +15,11 @@ background: linear-gradient(var(--tab-overlay), var(--tab-overlay)), var(--tab-base); } -[data-titlebar-tab][data-orientation="vertical"]:has([data-slot="project-avatar-slot"]) { +[data-titlebar-tab][data-orientation="vertical"]:has([data-slot="tab-project"]) { height: 45px; } -[data-titlebar-tab][data-orientation="vertical"]:has([data-slot="project-avatar-slot"]) [data-slot="tab-link"] { +[data-titlebar-tab][data-orientation="vertical"]:has([data-slot="tab-project"]) [data-slot="tab-link"] { display: grid; grid-template-columns: 16px minmax(0, 1fr); grid-template-rows: repeat(2, var(--line-height-compact)); diff --git a/packages/app/src/shell/titlebar/tab-nav.tsx b/packages/app/src/shell/titlebar/tab-nav.tsx index 1859b7bada2..2fe162ac206 100644 --- a/packages/app/src/shell/titlebar/tab-nav.tsx +++ b/packages/app/src/shell/titlebar/tab-nav.tsx @@ -14,6 +14,7 @@ import { SessionTabAvatar } from "@/shell/layout/session-tab-avatar" import { SessionProgressIndicatorV2 } from "@opencode-ai/session-ui/v2/session-progress-indicator-v2" import type { SessionInfo } from "@opencode-ai/client/promise" import { sessionLabel } from "@/session/title" +import { useSettings } from "@/settings/model" import { canOpenTabRename, forwardTabRef } from "./tab-gesture" import { TabPreviewPopover } from "./tab-popover" import "./tab-nav.css" @@ -39,6 +40,7 @@ export function TabNavItem(props: { orientation?: "horizontal" | "vertical" }) { const language = useLanguage() + const settings = useSettings() const [menu, setMenu] = createStore({ open: false, rename: false }) const [editing, setEditing] = createSignal(false) const [titleOverflowing, setTitleOverflowing] = createSignal(false) @@ -298,7 +300,7 @@ export function TabNavItem(props: { event.preventDefault() }} /> - + {(name) => ( {name()}