Skip to content

Commit d482df8

Browse files
committed
feat: add sidebar loader for thread lists
1 parent 177bdf7 commit d482df8

File tree

4 files changed

+81
-1
lines changed

4 files changed

+81
-1
lines changed

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ function MainApp() {
133133
approvals,
134134
threadsByWorkspace,
135135
threadStatusById,
136+
threadListLoadingByWorkspace,
136137
activeTurnIdByThread,
137138
tokenUsageByThread,
138139
rateLimitsByWorkspace,
@@ -321,6 +322,7 @@ function MainApp() {
321322
workspaces={workspaces}
322323
threadsByWorkspace={threadsByWorkspace}
323324
threadStatusById={threadStatusById}
325+
threadListLoadingByWorkspace={threadListLoadingByWorkspace}
324326
activeWorkspaceId={activeWorkspaceId}
325327
activeThreadId={activeThreadId}
326328
accountRateLimits={activeRateLimits}

src/components/Sidebar.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type SidebarProps = {
1111
string,
1212
{ isProcessing: boolean; hasUnread: boolean; isReviewing: boolean }
1313
>;
14+
threadListLoadingByWorkspace: Record<string, boolean>;
1415
activeWorkspaceId: string | null;
1516
activeThreadId: string | null;
1617
accountRateLimits: RateLimitSnapshot | null;
@@ -28,6 +29,7 @@ export function Sidebar({
2829
workspaces,
2930
threadsByWorkspace,
3031
threadStatusById,
32+
threadListLoadingByWorkspace,
3133
activeWorkspaceId,
3234
activeThreadId,
3335
accountRateLimits,
@@ -134,6 +136,10 @@ export function Sidebar({
134136
const threads = threadsByWorkspace[entry.id] ?? [];
135137
const isCollapsed = entry.settings.sidebarCollapsed;
136138
const showThreads = !isCollapsed && threads.length > 0;
139+
const isLoadingThreads =
140+
threadListLoadingByWorkspace[entry.id] ?? false;
141+
const showThreadLoader =
142+
!isCollapsed && isLoadingThreads && threads.length === 0;
137143

138144
return (
139145
<div key={entry.id} className="workspace-card">
@@ -274,6 +280,13 @@ export function Sidebar({
274280
)}
275281
</div>
276282
)}
283+
{showThreadLoader && (
284+
<div className="thread-loading" aria-label="Loading agents">
285+
<span className="thread-skeleton thread-skeleton-wide" />
286+
<span className="thread-skeleton" />
287+
<span className="thread-skeleton thread-skeleton-short" />
288+
</div>
289+
)}
277290
</div>
278291
);
279292
})}

src/hooks/useThreads.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type ThreadState = {
3737
string,
3838
{ isProcessing: boolean; hasUnread: boolean; isReviewing: boolean }
3939
>;
40+
threadListLoadingByWorkspace: Record<string, boolean>;
4041
activeTurnIdByThread: Record<string, string | null>;
4142
approvals: ApprovalRequest[];
4243
tokenUsageByThread: Record<string, ThreadTokenUsage>;
@@ -67,6 +68,11 @@ type ThreadAction =
6768
| { type: "appendReasoningContent"; threadId: string; itemId: string; delta: string }
6869
| { type: "appendToolOutput"; threadId: string; itemId: string; delta: string }
6970
| { type: "setThreads"; workspaceId: string; threads: ThreadSummary[] }
71+
| {
72+
type: "setThreadListLoading";
73+
workspaceId: string;
74+
isLoading: boolean;
75+
}
7076
| { type: "addApproval"; approval: ApprovalRequest }
7177
| { type: "removeApproval"; requestId: number }
7278
| { type: "setThreadTokenUsage"; threadId: string; tokenUsage: ThreadTokenUsage }
@@ -84,6 +90,7 @@ const initialState: ThreadState = {
8490
itemsByThread: emptyItems,
8591
threadsByWorkspace: {},
8692
threadStatusById: {},
93+
threadListLoadingByWorkspace: {},
8794
activeTurnIdByThread: {},
8895
approvals: [],
8996
tokenUsageByThread: {},
@@ -483,6 +490,14 @@ function threadReducer(state: ThreadState, action: ThreadAction): ThreadState {
483490
},
484491
};
485492
}
493+
case "setThreadListLoading":
494+
return {
495+
...state,
496+
threadListLoadingByWorkspace: {
497+
...state.threadListLoadingByWorkspace,
498+
[action.workspaceId]: action.isLoading,
499+
},
500+
};
486501
case "setThreadTokenUsage":
487502
return {
488503
...state,
@@ -1447,6 +1462,11 @@ export function useThreads({
14471462

14481463
const listThreadsForWorkspace = useCallback(
14491464
async (workspace: WorkspaceInfo) => {
1465+
dispatch({
1466+
type: "setThreadListLoading",
1467+
workspaceId: workspace.id,
1468+
isLoading: true,
1469+
});
14501470
onDebug?.({
14511471
id: `${Date.now()}-client-thread-list`,
14521472
timestamp: Date.now(),
@@ -1527,6 +1547,12 @@ export function useThreads({
15271547
label: "thread/list error",
15281548
payload: error instanceof Error ? error.message : String(error),
15291549
});
1550+
} finally {
1551+
dispatch({
1552+
type: "setThreadListLoading",
1553+
workspaceId: workspace.id,
1554+
isLoading: false,
1555+
});
15301556
}
15311557
},
15321558
[onDebug],
@@ -1808,6 +1834,7 @@ export function useThreads({
18081834
approvals: state.approvals,
18091835
threadsByWorkspace: state.threadsByWorkspace,
18101836
threadStatusById: state.threadStatusById,
1837+
threadListLoadingByWorkspace: state.threadListLoadingByWorkspace,
18111838
activeTurnIdByThread: state.activeTurnIdByThread,
18121839
tokenUsageByThread: state.tokenUsageByThread,
18131840
rateLimitsByWorkspace: state.rateLimitsByWorkspace,

src/styles/sidebar.css

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,36 @@
186186
display: flex;
187187
flex-direction: column;
188188
gap: 4px;
189-
margin-left: 12px;
189+
margin-left: 0;
190+
}
191+
192+
.thread-loading {
193+
display: flex;
194+
flex-direction: column;
195+
gap: 6px;
196+
padding: 6px 6px 4px;
197+
}
198+
199+
.thread-skeleton {
200+
height: 8px;
201+
width: 62%;
202+
border-radius: 999px;
203+
background: linear-gradient(
204+
110deg,
205+
rgba(255, 255, 255, 0.04) 8%,
206+
rgba(255, 255, 255, 0.18) 18%,
207+
rgba(255, 255, 255, 0.04) 33%
208+
);
209+
background-size: 200% 100%;
210+
animation: shimmer 1.4s ease-in-out infinite;
211+
}
212+
213+
.thread-skeleton-wide {
214+
width: 78%;
215+
}
216+
217+
.thread-skeleton-short {
218+
width: 44%;
190219
}
191220

192221
.thread-row {
@@ -338,3 +367,12 @@
338367
opacity: 0.6;
339368
}
340369
}
370+
371+
@keyframes shimmer {
372+
0% {
373+
background-position: 200% 0;
374+
}
375+
100% {
376+
background-position: -200% 0;
377+
}
378+
}

0 commit comments

Comments
 (0)