summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Hill <[email protected]>2026-03-17 19:54:14 +0000
committerGitHub <[email protected]>2026-03-17 19:54:14 +0000
commit7daea69e13e5a17278fe244273fdeb141b0369d6 (patch)
tree4d421611992fb686a052b72ff928711f6805dc35
parent0772a9591807d15c369c37edc01b9018bdc6e7d1 (diff)
downloadopencode-7daea69e13e5a17278fe244273fdeb141b0369d6.tar.gz
opencode-7daea69e13e5a17278fe244273fdeb141b0369d6.zip
tweak(ui): add an empty state to the sidebar when no projects (#17971)
Co-authored-by: Shoubhit Dash <[email protected]>
-rw-r--r--packages/app/e2e/app/home.spec.ts3
-rw-r--r--packages/app/src/components/titlebar.tsx63
-rw-r--r--packages/app/src/i18n/en.ts2
-rw-r--r--packages/app/src/pages/layout.tsx30
4 files changed, 60 insertions, 38 deletions
diff --git a/packages/app/e2e/app/home.spec.ts b/packages/app/e2e/app/home.spec.ts
index a3cedf7cb..5deba4300 100644
--- a/packages/app/e2e/app/home.spec.ts
+++ b/packages/app/e2e/app/home.spec.ts
@@ -3,8 +3,11 @@ import { serverNamePattern } from "../utils"
test("home renders and shows core entrypoints", async ({ page }) => {
await page.goto("/")
+ const nav = page.locator('[data-component="sidebar-nav-desktop"]')
await expect(page.getByRole("button", { name: "Open project" }).first()).toBeVisible()
+ await expect(nav.getByText("No projects open")).toBeVisible()
+ await expect(nav.getByText("Open a project to get started")).toBeVisible()
await expect(page.getByRole("button", { name: serverNamePattern })).toBeVisible()
})
diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx
index 345903420..77de1a73c 100644
--- a/packages/app/src/components/titlebar.tsx
+++ b/packages/app/src/components/titlebar.tsx
@@ -77,6 +77,7 @@ export function Titlebar() {
const canBack = createMemo(() => history.index > 0)
const canForward = createMemo(() => history.index < history.stack.length - 1)
+ const hasProjects = createMemo(() => layout.projects.list().length > 0)
const back = () => {
const next = backPath(history)
@@ -251,36 +252,38 @@ export function Titlebar() {
</div>
</div>
</Show>
- <div
- class="flex items-center gap-0 transition-transform"
- classList={{
- "translate-x-0": !layout.sidebar.opened(),
- "-translate-x-[36px]": layout.sidebar.opened(),
- "duration-180 ease-out": !layout.sidebar.opened(),
- "duration-180 ease-in": layout.sidebar.opened(),
- }}
- >
- <Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
- <Button
- variant="ghost"
- icon="chevron-left"
- class="titlebar-icon w-6 h-6 p-0 box-border"
- disabled={!canBack()}
- onClick={back}
- aria-label={language.t("common.goBack")}
- />
- </Tooltip>
- <Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
- <Button
- variant="ghost"
- icon="chevron-right"
- class="titlebar-icon w-6 h-6 p-0 box-border"
- disabled={!canForward()}
- onClick={forward}
- aria-label={language.t("common.goForward")}
- />
- </Tooltip>
- </div>
+ <Show when={hasProjects()}>
+ <div
+ class="flex items-center gap-0 transition-transform"
+ classList={{
+ "translate-x-0": !layout.sidebar.opened(),
+ "-translate-x-[36px]": layout.sidebar.opened(),
+ "duration-180 ease-out": !layout.sidebar.opened(),
+ "duration-180 ease-in": layout.sidebar.opened(),
+ }}
+ >
+ <Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
+ <Button
+ variant="ghost"
+ icon="chevron-left"
+ class="titlebar-icon w-6 h-6 p-0 box-border"
+ disabled={!canBack()}
+ onClick={back}
+ aria-label={language.t("common.goBack")}
+ />
+ </Tooltip>
+ <Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
+ <Button
+ variant="ghost"
+ icon="chevron-right"
+ class="titlebar-icon w-6 h-6 p-0 box-border"
+ disabled={!canForward()}
+ onClick={forward}
+ aria-label={language.t("common.goForward")}
+ />
+ </Tooltip>
+ </div>
+ </Show>
</div>
</div>
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts
index ad12e1e0d..7f6816de9 100644
--- a/packages/app/src/i18n/en.ts
+++ b/packages/app/src/i18n/en.ts
@@ -674,6 +674,8 @@ export const dict = {
"sidebar.project.recentSessions": "Recent sessions",
"sidebar.project.viewAllSessions": "View all sessions",
"sidebar.project.clearNotifications": "Clear notifications",
+ "sidebar.empty.title": "No projects open",
+ "sidebar.empty.description": "Open a project to get started",
"debugBar.ariaLabel": "Development performance diagnostics",
"debugBar.na": "n/a",
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index ab2687dca..a694ce094 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -1959,6 +1959,7 @@ export default function Layout(props: ParentProps) {
const merged = createMemo(() => panelProps.mobile || (panelProps.merged ?? layout.sidebar.opened()))
const hover = createMemo(() => !panelProps.mobile && panelProps.merged === false && !layout.sidebar.opened())
const popover = createMemo(() => !!panelProps.mobile || panelProps.merged === false || layout.sidebar.opened())
+ const empty = createMemo(() => !params.dir && layout.projects.list().length === 0)
const projectName = createMemo(() => {
const item = project()
if (!item) return ""
@@ -2011,7 +2012,26 @@ export default function Layout(props: ParentProps) {
width: panelProps.mobile ? undefined : `${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px`,
}}
>
- <Show when={project()}>
+ <Show
+ when={project()}
+ fallback={
+ <Show when={empty()}>
+ <div class="flex-1 min-h-0 -mt-4 flex items-center justify-center px-6 pb-64 text-center">
+ <div class="mt-8 flex max-w-60 flex-col items-center gap-6 text-center">
+ <div class="flex flex-col gap-3">
+ <div class="text-14-medium text-text-strong">{language.t("sidebar.empty.title")}</div>
+ <div class="text-14-regular text-text-base" style={{ "line-height": "var(--line-height-normal)" }}>
+ {language.t("sidebar.empty.description")}
+ </div>
+ </div>
+ <Button size="large" icon="folder-add-left" onClick={chooseProject}>
+ {language.t("command.project.open")}
+ </Button>
+ </div>
+ </div>
+ </Show>
+ }
+ >
<>
<div class="shrink-0 pl-1 py-1">
<div class="group/project flex items-start justify-between gap-2 py-2 pl-2 pr-0">
@@ -2260,13 +2280,7 @@ export default function Layout(props: ParentProps) {
helpLabel={() => language.t("sidebar.help")}
onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
renderPanel={() =>
- mobile ? (
- <SidebarPanel project={currentProject} mobile />
- ) : (
- <Show when={currentProject()}>
- <SidebarPanel project={currentProject} merged />
- </Show>
- )
+ mobile ? <SidebarPanel project={currentProject} mobile /> : <SidebarPanel project={currentProject} merged />
}
/>
)