Skip to content

Commit ef8e072

Browse files
GenerQAQclaude
andcommitted
feat(dashboard): progressive loading — show first page immediately
Render the first page of results after a single API call instead of blocking until all cursor pages have been fetched. Remaining pages load in the background while users can already browse loaded data. - Add `isLoading` prop to both PaginationBar components (commercial + OSS) showing a Loader2 spinner next to the item count - Convert all 15 cursor-loop pages to the progressive pattern: fetch first page → render → fetch rest in background - Item count and page buttons update live as more data arrives Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3202519 commit ef8e072

17 files changed

Lines changed: 360 additions & 227 deletions

File tree

dashboard/app/project/[id]/agent-skills/agent-skills-page-client.tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export function AgentSkillsPageClient({
9393

9494
const [skills, setSkills] = useState<AgentSkillListItem[]>([]);
9595
const [isLoadingSkills, setIsLoadingSkills] = useState(true);
96+
const [isLoadingMore, setIsLoadingMore] = useState(false);
9697
const [currentPage, setCurrentPage] = useState(1);
9798

9899
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
@@ -144,24 +145,26 @@ export function AgentSkillsPageClient({
144145
setIsLoadingSkills(true);
145146
const userParam = userFilter === "all" ? undefined : userFilter;
146147

147-
const allSkills: AgentSkillListItem[] = [];
148-
let cursor: string | undefined = undefined;
149-
let hasMore = true;
148+
const first = await getAgentSkills(project.id, 50, undefined, true, userParam);
149+
setSkills(first.items || []);
150+
setCurrentPage(1);
151+
setIsLoadingSkills(false);
150152

151-
while (hasMore) {
152-
const res = await getAgentSkills(project.id, 50, cursor, true, userParam);
153-
allSkills.push(...(res.items || []));
154-
cursor = res.next_cursor;
155-
hasMore = res.has_more || false;
153+
if (first.has_more) {
154+
setIsLoadingMore(true);
155+
let cursor = first.next_cursor;
156+
while (cursor) {
157+
const res = await getAgentSkills(project.id, 50, cursor, true, userParam);
158+
setSkills(prev => [...prev, ...(res.items || [])]);
159+
cursor = res.has_more ? res.next_cursor : undefined;
160+
}
161+
setIsLoadingMore(false);
156162
}
157-
158-
setSkills(allSkills);
159-
setCurrentPage(1);
160163
} catch (error) {
161164
console.error("Failed to load skills:", error);
162165
toast.error("Failed to load agent skills");
163-
} finally {
164166
setIsLoadingSkills(false);
167+
setIsLoadingMore(false);
165168
}
166169
}, [project.id, userFilter]);
167170

@@ -451,6 +454,7 @@ export function AgentSkillsPageClient({
451454
totalItems={filteredSkills.length}
452455
onPageChange={setCurrentPage}
453456
itemLabel="skills"
457+
isLoading={isLoadingMore}
454458
/>
455459
</>
456460
)}

dashboard/app/project/[id]/disk/disk-page-client.tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ export function DiskPageClient({
131131
const [disks, setDisks] = useState<Disk[]>([]);
132132
const [selectedDisk, setSelectedDisk] = useState<Disk | null>(null);
133133
const [isLoadingDisks, setIsLoadingDisks] = useState(true);
134+
const [isLoadingMoreDisks, setIsLoadingMoreDisks] = useState(false);
134135
const [currentPage, setCurrentPage] = useState(1);
135136

136137
// File preview states
@@ -234,24 +235,26 @@ export function DiskPageClient({
234235
setIsLoadingDisks(true);
235236
const userParam = userFilter === "all" ? undefined : userFilter;
236237

237-
const allDisks: Disk[] = [];
238-
let cursor: string | undefined = undefined;
239-
let hasMore = true;
238+
const first = await getDisks(project.id, 50, undefined, true, userParam);
239+
setDisks(first.items || []);
240+
setCurrentPage(1);
241+
setIsLoadingDisks(false);
240242

241-
while (hasMore) {
242-
const res = await getDisks(project.id, 50, cursor, true, userParam);
243-
allDisks.push(...(res.items || []));
244-
cursor = res.next_cursor;
245-
hasMore = res.has_more || false;
243+
if (first.has_more) {
244+
setIsLoadingMoreDisks(true);
245+
let cursor = first.next_cursor;
246+
while (cursor) {
247+
const res = await getDisks(project.id, 50, cursor, true, userParam);
248+
setDisks(prev => [...prev, ...(res.items || [])]);
249+
cursor = res.has_more ? res.next_cursor : undefined;
250+
}
251+
setIsLoadingMoreDisks(false);
246252
}
247-
248-
setDisks(allDisks);
249-
setCurrentPage(1);
250253
} catch (error) {
251254
console.error("Failed to load disks:", error);
252255
toast.error("Failed to load disks");
253-
} finally {
254256
setIsLoadingDisks(false);
257+
setIsLoadingMoreDisks(false);
255258
}
256259
}, [project.id, userFilter]);
257260

@@ -934,6 +937,7 @@ export function DiskPageClient({
934937
totalItems={filteredDisks.length}
935938
onPageChange={setCurrentPage}
936939
itemLabel="disks"
940+
isLoading={isLoadingMoreDisks}
937941
/>
938942
</>
939943
)}

dashboard/app/project/[id]/learning-spaces/learning-spaces-page-client.tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export function LearningSpacesPageClient({
9191

9292
const [spaces, setSpaces] = useState<LearningSpace[]>([]);
9393
const [isLoading, setIsLoading] = useState(true);
94+
const [isLoadingMore, setIsLoadingMore] = useState(false);
9495
const [isRefreshing, setIsRefreshing] = useState(false);
9596
const [currentPage, setCurrentPage] = useState(1);
9697

@@ -156,24 +157,26 @@ export function LearningSpacesPageClient({
156157
setIsLoading(true);
157158
const userParam = userFilter === "all" ? undefined : userFilter;
158159

159-
const allSpaces: LearningSpace[] = [];
160-
let cursor: string | undefined = undefined;
161-
let hasMore = true;
160+
const first = await getLearningSpaces(projectId, 50, undefined, true, userParam);
161+
setSpaces(first.items || []);
162+
setCurrentPage(1);
163+
setIsLoading(false);
162164

163-
while (hasMore) {
164-
const res = await getLearningSpaces(projectId, 50, cursor, true, userParam);
165-
allSpaces.push(...(res.items || []));
166-
cursor = res.next_cursor;
167-
hasMore = res.has_more || false;
165+
if (first.has_more) {
166+
setIsLoadingMore(true);
167+
let cursor = first.next_cursor;
168+
while (cursor) {
169+
const res = await getLearningSpaces(projectId, 50, cursor, true, userParam);
170+
setSpaces(prev => [...prev, ...(res.items || [])]);
171+
cursor = res.has_more ? res.next_cursor : undefined;
172+
}
173+
setIsLoadingMore(false);
168174
}
169-
170-
setSpaces(allSpaces);
171-
setCurrentPage(1);
172175
} catch (error) {
173176
console.error("Failed to load learning spaces:", error);
174177
toast.error("Failed to load learning spaces");
175-
} finally {
176178
setIsLoading(false);
179+
setIsLoadingMore(false);
177180
}
178181
}, [projectId, userFilter]);
179182

@@ -536,6 +539,7 @@ export function LearningSpacesPageClient({
536539
totalItems={filteredSpaces.length}
537540
onPageChange={setCurrentPage}
538541
itemLabel="spaces"
542+
isLoading={isLoadingMore}
539543
/>
540544
</div>
541545
)}

dashboard/app/project/[id]/sandbox/sandbox-page-client.tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export function SandboxPageClient({
4343
const [sandboxes, setSandboxes] = useState<SandboxLog[]>([]);
4444
const [selectedSandbox, setSelectedSandbox] = useState<SandboxLog | null>(null);
4545
const [isLoadingSandboxes, setIsLoadingSandboxes] = useState(true);
46+
const [isLoadingMore, setIsLoadingMore] = useState(false);
4647
const [currentPage, setCurrentPage] = useState(1);
4748

4849
// Refresh states
@@ -89,24 +90,26 @@ export function SandboxPageClient({
8990
try {
9091
setIsLoadingSandboxes(true);
9192

92-
const allSandboxes: SandboxLog[] = [];
93-
let cursor: string | undefined = undefined;
94-
let hasMore = true;
93+
const first = await getSandboxLogs(project.id, 50, undefined, true);
94+
setSandboxes(first.items || []);
95+
setCurrentPage(1);
96+
setIsLoadingSandboxes(false);
9597

96-
while (hasMore) {
97-
const res = await getSandboxLogs(project.id, 50, cursor, true);
98-
allSandboxes.push(...(res.items || []));
99-
cursor = res.next_cursor;
100-
hasMore = res.has_more || false;
98+
if (first.has_more) {
99+
setIsLoadingMore(true);
100+
let cursor = first.next_cursor;
101+
while (cursor) {
102+
const res = await getSandboxLogs(project.id, 50, cursor, true);
103+
setSandboxes(prev => [...prev, ...(res.items || [])]);
104+
cursor = res.has_more ? res.next_cursor : undefined;
105+
}
106+
setIsLoadingMore(false);
101107
}
102-
103-
setSandboxes(allSandboxes);
104-
setCurrentPage(1);
105108
} catch (error) {
106109
console.error("Failed to load sandboxes:", error);
107110
toast.error("Failed to load sandboxes");
108-
} finally {
109111
setIsLoadingSandboxes(false);
112+
setIsLoadingMore(false);
110113
}
111114
}, [project.id]);
112115

@@ -236,6 +239,7 @@ export function SandboxPageClient({
236239
totalItems={filteredSandboxes.length}
237240
onPageChange={setCurrentPage}
238241
itemLabel="sandboxes"
242+
isLoading={isLoadingMore}
239243
/>
240244
</>
241245
)}

dashboard/app/project/[id]/session/[sessionId]/messages/messages-page-client.tsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ export function MessagesPageClient({
241241
const [allMessages, setAllMessages] = useState<Message[]>([]);
242242
const [allEvents, setAllEvents] = useState<SessionEvent[]>([]);
243243
const [isLoadingMessages, setIsLoadingMessages] = useState(false);
244+
const [isLoadingMoreMessages, setIsLoadingMoreMessages] = useState(false);
244245
const [isRefreshingMessages, setIsRefreshingMessages] = useState(false);
245246
const [currentPage, setCurrentPage] = useState(1);
246247

@@ -302,26 +303,33 @@ export function MessagesPageClient({
302303
const loadAllMessages = async () => {
303304
try {
304305
setIsLoadingMessages(true);
305-
const allMsgs: Message[] = [];
306306
const allEvts: SessionEvent[] = [];
307307
const allPublicUrls: Record<string, { url: string; expire_at: string }> = {};
308-
let cursor: string | undefined = undefined;
309-
let hasMore = true;
310308

311-
while (hasMore) {
312-
const res = await getMessages(project.id, sessionId, 50, cursor);
313-
allMsgs.push(...(res.items || []));
314-
if (res.events) {
315-
allEvts.push(...res.events);
316-
}
317-
if (res.public_urls) {
318-
Object.assign(allPublicUrls, res.public_urls);
309+
const first = await getMessages(project.id, sessionId, 50, undefined);
310+
setAllMessages(first.items || []);
311+
if (first.events) allEvts.push(...first.events);
312+
if (first.public_urls) Object.assign(allPublicUrls, first.public_urls);
313+
setMessagePublicUrls({ ...allPublicUrls });
314+
setCurrentPage(1);
315+
setIsLoadingMessages(false);
316+
317+
if (first.has_more) {
318+
setIsLoadingMoreMessages(true);
319+
let cursor = first.next_cursor;
320+
while (cursor) {
321+
const res = await getMessages(project.id, sessionId, 50, cursor);
322+
setAllMessages(prev => [...prev, ...(res.items || [])]);
323+
if (res.events) allEvts.push(...res.events);
324+
if (res.public_urls) {
325+
Object.assign(allPublicUrls, res.public_urls);
326+
setMessagePublicUrls({ ...allPublicUrls });
327+
}
328+
cursor = res.has_more ? res.next_cursor : undefined;
319329
}
320-
cursor = res.next_cursor;
321-
hasMore = res.has_more || false;
330+
setIsLoadingMoreMessages(false);
322331
}
323332

324-
setAllMessages(allMsgs);
325333
const seenIds = new Set<string>();
326334
const dedupedEvts = allEvts.filter((e) => {
327335
if (seenIds.has(e.id)) return false;
@@ -330,12 +338,11 @@ export function MessagesPageClient({
330338
});
331339
setAllEvents(dedupedEvts);
332340
setMessagePublicUrls(allPublicUrls);
333-
setCurrentPage(1);
334341
} catch (error) {
335342
console.error("Failed to load messages:", error);
336343
toast.error("Failed to load messages");
337-
} finally {
338344
setIsLoadingMessages(false);
345+
setIsLoadingMoreMessages(false);
339346
}
340347
};
341348

@@ -886,6 +893,7 @@ export function MessagesPageClient({
886893
totalItems={filteredTimelineItems.length}
887894
onPageChange={setCurrentPage}
888895
itemLabel="messages"
896+
isLoading={isLoadingMoreMessages}
889897
/>
890898
</>
891899
)}

dashboard/app/project/[id]/session/[sessionId]/task/task-page-client.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export function TaskPageClient({
5252
const [sessionInfo, setSessionInfo] = useState<string>("");
5353
const [allTasks, setAllTasks] = useState<Task[]>([]);
5454
const [isLoadingTasks, setIsLoadingTasks] = useState(false);
55+
const [isLoadingMore, setIsLoadingMore] = useState(false);
5556
const [isRefreshingTasks, setIsRefreshingTasks] = useState(false);
5657
const [currentPage, setCurrentPage] = useState(1);
5758

@@ -82,24 +83,27 @@ export function TaskPageClient({
8283
const loadAllTasks = async () => {
8384
try {
8485
setIsLoadingTasks(true);
85-
const allTsks: Task[] = [];
86-
let cursor: string | undefined = undefined;
87-
let hasMore = true;
8886

89-
while (hasMore) {
90-
const res = await getTasks(project.id, sessionId, 50, cursor);
91-
allTsks.push(...(res.items || []));
92-
cursor = res.next_cursor;
93-
hasMore = res.has_more || false;
94-
}
95-
96-
setAllTasks(allTsks);
87+
const first = await getTasks(project.id, sessionId, 50, undefined);
88+
setAllTasks(first.items || []);
9789
setCurrentPage(1);
90+
setIsLoadingTasks(false);
91+
92+
if (first.has_more) {
93+
setIsLoadingMore(true);
94+
let cursor = first.next_cursor;
95+
while (cursor) {
96+
const res = await getTasks(project.id, sessionId, 50, cursor);
97+
setAllTasks(prev => [...prev, ...(res.items || [])]);
98+
cursor = res.has_more ? res.next_cursor : undefined;
99+
}
100+
setIsLoadingMore(false);
101+
}
98102
} catch (error) {
99103
console.error("Failed to load tasks:", error);
100104
toast.error("Failed to load tasks");
101-
} finally {
102105
setIsLoadingTasks(false);
106+
setIsLoadingMore(false);
103107
}
104108
};
105109

@@ -275,6 +279,7 @@ export function TaskPageClient({
275279
totalItems={allTasks.length}
276280
onPageChange={setCurrentPage}
277281
itemLabel="tasks"
282+
isLoading={isLoadingMore}
278283
/>
279284
</>
280285
)}

0 commit comments

Comments
 (0)