authorgravatar for 91240327+shivamhwp@users.noreply.github.comshivam <91240327+shivamhwp@users.noreply.github.com> 2026-03-30 00:17:51+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-03-29 11:47:51-07:00
log7b676b76de536b0a9e60fd8bc6e20404815d6294
treeb649b601f208b45d6e3b8b5511170b86cba38a39
parentd5f2333173f6b01e38d6488dd7a2f157b479195f
signature Signed by PGP key B5690EEEBB952194

fix: show empty project thread state in sidebar (#1546)


2 files changed, 26 insertions(+), 0 deletions(-)

apps/web/src/components/ChatView.browser.tsx+13
...@@ -1041,6 +1041,19 @@ describe("ChatView timeline estimator parity (full app)", () => {...@@ -1041,6 +1041,19 @@ describe("ChatView timeline estimator parity (full app)", () => {
1041 },1041 },
1042 );1042 );
10431043
1044 it("shows an explicit empty state for projects without threads in the sidebar", async () => {
1045 const mounted = await mountChatView({
1046 viewport: DEFAULT_VIEWPORT,
1047 snapshot: createDraftOnlySnapshot(),
1048 });
1049
1050 try {
1051 await expect.element(page.getByText("No threads yet")).toBeInTheDocument();
1052 } finally {
1053 await mounted.cleanup();
1054 }
1055 });
1056
1044 it("opens the project cwd for draft threads without a worktree path", async () => {1057 it("opens the project cwd for draft threads without a worktree path", async () => {
1045 setDraftThreadWithoutWorktree();1058 setDraftThreadWithoutWorktree();
10461059
apps/web/src/components/Sidebar.tsx+13
...@@ -1098,6 +1098,7 @@ export default function Sidebar() {...@@ -1098,6 +1098,7 @@ export default function Sidebar() {
1098 const renderedThreads = pinnedCollapsedThread1098 const renderedThreads = pinnedCollapsedThread
1099 ? [pinnedCollapsedThread]1099 ? [pinnedCollapsedThread]
1100 : visibleProjectThreads;1100 : visibleProjectThreads;
1101 const showEmptyThreadState = project.expanded && projectThreads.length === 0;
11011102
1102 return {1103 return {
1103 hasHiddenThreads,1104 hasHiddenThreads,
...@@ -1108,6 +1109,7 @@ export default function Sidebar() {...@@ -1108,6 +1109,7 @@ export default function Sidebar() {
1108 projectThreads,1109 projectThreads,
1109 threadStatuses,1110 threadStatuses,
1110 renderedThreads,1111 renderedThreads,
1112 showEmptyThreadState,
1111 shouldShowThreadPanel,1113 shouldShowThreadPanel,
1112 isThreadListExpanded,1114 isThreadListExpanded,
1113 };1115 };
...@@ -1255,6 +1257,7 @@ export default function Sidebar() {...@@ -1255,6 +1257,7 @@ export default function Sidebar() {
1255 projectThreads,1257 projectThreads,
1256 threadStatuses,1258 threadStatuses,
1257 renderedThreads,1259 renderedThreads,
1260 showEmptyThreadState,
1258 shouldShowThreadPanel,1261 shouldShowThreadPanel,
1259 isThreadListExpanded,1262 isThreadListExpanded,
1260 } = renderedProject;1263 } = renderedProject;
...@@ -1572,6 +1575,16 @@ export default function Sidebar() {...@@ -1572,6 +1575,16 @@ export default function Sidebar() {
1572 ref={attachThreadListAutoAnimateRef}1575 ref={attachThreadListAutoAnimateRef}
1573 className="mx-1 my-0 w-full translate-x-0 gap-0.5 overflow-hidden px-1.5 py-0"1576 className="mx-1 my-0 w-full translate-x-0 gap-0.5 overflow-hidden px-1.5 py-0"
1574 >1577 >
1578 {shouldShowThreadPanel && showEmptyThreadState ? (
1579 <SidebarMenuSubItem className="w-full" data-thread-selection-safe>
1580 <div
1581 data-thread-selection-safe
1582 className="flex h-6 w-full translate-x-0 items-center px-2 text-left text-[10px] text-muted-foreground/60"
1583 >
1584 <span>No threads yet</span>
1585 </div>
1586 </SidebarMenuSubItem>
1587 ) : null}
1575 {shouldShowThreadPanel && renderedThreads.map((thread) => renderThreadRow(thread))}1588 {shouldShowThreadPanel && renderedThreads.map((thread) => renderThreadRow(thread))}
15761589
1577 {project.expanded && hasHiddenThreads && !isThreadListExpanded && (1590 {project.expanded && hasHiddenThreads && !isThreadListExpanded && (