diff --git a/docs/design.md b/docs/design.md
index 541fe9c6f5..e0904d917e 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -193,7 +193,7 @@ Terminology:
Loading is inline by default. ` ` sits next to the thing it relates to (`packages/app/src/screens/settings/providers-section.tsx:227-231`). Page-level loading is a centered `` (`packages/app/src/screens/sessions-screen.tsx:69-72`). Card-level loading is a single short line, not a spinner. In-row dropdown items use ``; the menu item handles its own pending state.
-Empty states are short noun phrases. Centered, muted, one or two lines. Sessions screen pairs the empty noun with a single ghost button to navigate back (`packages/app/src/screens/sessions-screen.tsx:74-81`); that pairing is the maximum elaboration. Illustrations and CTAs disguised as empty states are wrong.
+Screen-level empty states use a centered, muted noun phrase and at most one ghost button (`packages/app/src/screens/sessions-screen.tsx`). Sidebar list empty states use a quiet bordered card with a left-aligned `sm` title, a muted `sm` description, and `xs` `` actions. Use `secondary` for the primary action and `outline` for a secondary action (`packages/app/src/components/sidebar/empty-states.tsx`). Keep both forms short and free of illustrations.
Inline errors are a single sentence in `palette.red[300]` `xs`, sitting under the field or inside the card it relates to (`packages/app/src/screens/settings/providers-section.tsx:115-119`).
diff --git a/docs/qa-evidence/sidebar-empty-state-card/after-compact.png b/docs/qa-evidence/sidebar-empty-state-card/after-compact.png
new file mode 100644
index 0000000000..140bcf2052
Binary files /dev/null and b/docs/qa-evidence/sidebar-empty-state-card/after-compact.png differ
diff --git a/docs/qa-evidence/sidebar-empty-state-card/after-dark.png b/docs/qa-evidence/sidebar-empty-state-card/after-dark.png
new file mode 100644
index 0000000000..8a2c4bf8ae
Binary files /dev/null and b/docs/qa-evidence/sidebar-empty-state-card/after-dark.png differ
diff --git a/docs/qa-evidence/sidebar-empty-state-card/after-light.png b/docs/qa-evidence/sidebar-empty-state-card/after-light.png
new file mode 100644
index 0000000000..e283932a5b
Binary files /dev/null and b/docs/qa-evidence/sidebar-empty-state-card/after-light.png differ
diff --git a/docs/qa-evidence/sidebar-empty-state-card/before-dark.png b/docs/qa-evidence/sidebar-empty-state-card/before-dark.png
new file mode 100644
index 0000000000..0b5269331f
Binary files /dev/null and b/docs/qa-evidence/sidebar-empty-state-card/before-dark.png differ
diff --git a/docs/qa-evidence/sidebar-empty-state-card/before-light.png b/docs/qa-evidence/sidebar-empty-state-card/before-light.png
new file mode 100644
index 0000000000..95ce14ad3a
Binary files /dev/null and b/docs/qa-evidence/sidebar-empty-state-card/before-light.png differ
diff --git a/packages/app/src/components/sidebar/empty-states.tsx b/packages/app/src/components/sidebar/empty-states.tsx
index 4aaf93e0cc..3328ed2c5c 100644
--- a/packages/app/src/components/sidebar/empty-states.tsx
+++ b/packages/app/src/components/sidebar/empty-states.tsx
@@ -1,4 +1,4 @@
-import { useCallback } from "react";
+import { type ReactNode, useCallback } from "react";
import { Import, Plus } from "lucide-react-native";
import { useTranslation } from "react-i18next";
import { Text, View } from "react-native";
@@ -6,6 +6,28 @@ import { StyleSheet } from "react-native-unistyles";
import { Button } from "@/components/ui/button";
import { useSidebarViewStore } from "@/stores/sidebar-view-store";
+function SidebarEmptyStateCard({
+ testID,
+ title,
+ description,
+ children,
+}: {
+ testID: string;
+ title: string;
+ description: string;
+ children: ReactNode;
+}) {
+ return (
+
+
+ {title}
+ {description}
+
+ {children}
+
+ );
+}
+
/**
* The two things the sidebar's workspace list says when it has no rows.
*
@@ -27,13 +49,15 @@ export function SidebarFilterEmptyState() {
}, [clearLabelFilter, clearProjectFilters]);
return (
-
- {t("sidebar.filterEmpty.title")}
- {t("sidebar.filterEmpty.description")}
-
+
+
{t("sidebar.filterEmpty.clear")}
-
+
);
}
@@ -47,40 +71,44 @@ export function SidebarProjectEmptyState({
const { t } = useTranslation();
return (
-
- {t("sidebar.project.empty.title")}
- {t("sidebar.project.empty.description")}
-
+
+
{t("sidebar.actions.addProject")}
-
+
{t("importSession.title")}
-
+
);
}
const styles = StyleSheet.create((theme) => ({
- container: {
- marginHorizontal: theme.spacing[2],
- marginTop: theme.spacing[4],
- paddingTop: theme.spacing[6],
- paddingBottom: theme.spacing[4],
- paddingHorizontal: theme.spacing[4],
+ card: {
+ marginTop: theme.spacing[3],
+ padding: theme.spacing[4],
+ borderWidth: 1,
+ borderColor: theme.colors.border,
borderRadius: theme.borderRadius.lg,
- backgroundColor: theme.colors.surface0,
- alignItems: "center",
- gap: theme.spacing[3],
+ },
+ copy: {
+ gap: theme.spacing[1],
+ },
+ actions: {
+ flexDirection: "row",
+ gap: theme.spacing[2],
+ marginTop: theme.spacing[4],
},
title: {
color: theme.colors.foreground,
fontSize: theme.fontSize.sm,
- fontWeight: theme.fontWeight.medium,
- textAlign: "center",
+ fontWeight: theme.fontWeight.normal,
},
description: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.sm,
- textAlign: "center",
},
}));